《Javascript》高级程序设计 第五章 详细解释引用类型(下)

我的文章会在我的Blog同步更新,Blog刚搭了几天,用于记录我的学习过程:Million

<h2>5.5Function类型</h2>
作者说ECMAScript中最有意思的是函数,有意思的根源在于,函数实际上是对象。每个函数都是Function类型的实例
与其他引用类型一样,函数类型也有属性和方法。由于函数是对象,因此函数名实际上只是一个指向函数对象的指针,不会与某个函数绑定

函数通常是使用函数声明语法定义的,如下面的例子所示:

function sum(num1,num2){
     return num1+num2;
}

这与下面使用函数表达式定义函数的方法几乎相差无几:

var sum = function(num1,num2){
     return num1+num2;
};   // ← 注意最后的分号,声明完变量必加的分号

第二段代码定义了变量sum并将其初始化为一个函数(注意这句话的顺序,把一个定义好的变量初始化为一个函数)。我们可以发现用函数表达式创建的函数,function关键字后面没有函数名。因为没有必要。通过变量sum即可以引用到函数(注意,你不能说变量sum是函数名)。另外,注意函数末尾有一个分号,因为说到底sum还是个变量,只是变量的值是一个函数!


最后一种定义函数的方式,因为函数是对象,所以可以使用Function构造函数。Function可以接收任意个数量的参数,最后一个参数始终被当作函数体,前面的都是函数的参数。例子:

var sum = new Function("num1","num2","num3","return num1+num2");   //不推荐

从技术的角度讲,这是一个函数表达式,但是不推荐使用。因为这种语法会导致解析两次代码(第一次是解析常规ECMAScript,第二次是解析传入构造函数中的字符串),从而影响性能。不过这种语法有利于理解“函数是对象,函数名是指针”这个概念。


由于函数名仅仅是指针(好唏嘘,函数名以为用它的名字创建的函数就是它的,结果,它只是一个指针。)一个对象可以有很多指针,所以一个函数也可以有多个名字。例子:

function sum(num1,num2){
     return num1+num2;
}

var anotherSum = sum;
alert(anotherSum(10,10));    //20

sum = null;
alert(anotherSum(10,10));     //20

即使把sum给设置为null,但因为这个指针已经复制了一份给anotherSum,所以这个函数对象还是可以被引用到,不会因为sum不见了,函数就跟着没了。(亲测PHP似乎就不能用这种方法“转换函数名”)注意,使用不带圆括号的函数名是访问函数指针,使用带括号的函数名就表示要调用这个函数


<h3>5.5.1没有重载</h3>
把函数名想象为指针,就能理解为什么ECMAScript没有重载。例子:

function sum(num1,num2){
    return num1+100;
}
function sum(num1){
    return num1+200;
}

console.log(sum(100,100));   // 300

显然,例子中声明了两个同名函数,而结果是后面的函数覆盖了前面的函数。因为在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。


<h3>5.5.2函数声明与函数表达式</h3>
实际上,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁解析器会率先读取函数声明并使其在执行任何代码之前可用(可以访问);至于函数表达式,就必须等到解析器执行到它所在的代码行,才会真正被解释执行。(所以函数声明有函数声明提升(function declaration hoisting),函数表达式没有)函数表达式不能写在要调用这个函数的代码之后。
函数声明提升:Javascript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以即使声明函数的代码在调用它的代码后面。JavaScript也能把函数声明提升到顶部。
除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。再无其他差别。

✎ 有人觉得不妨在使用函数表达式的同时给函数命名,like this :

var sum = function sum(){ }
但这种语法在 Safari 中会出错。

<h3>5.5.3作为值的函数</h3>
因为ECMAScript中的函数名本身就是变量,所以函数也可以作为一个值来使用。也就是说,不仅可以像传递参数一样把一个函数当作参数传递给一个函数,而且可以将一个函数作为另一个函数的结果返回。例子:

function callSomeFunction(someFunc,someArgument){
     return someFunc(someArgument)
}

这个函数接收两个参数,一个是函数,一个是普通参数,这个普通参数给传进来的函数使用。然后,就可以像下面这样传递函数了:

function add10(num){
    return num+10;
}
var result1 = callSomeFunction(add10,10);   //注意函数作为参数传递是不带括号的,带括号的函数会执行,参数就变成这个函数执行完成返回的那个值了
alert(result1);                             //20               

function getGreeting(name){
    return "Hello " + name;
}

var result2 = callSomeFunction(getGreeting,"Nicholas");
alert(result2);                              // Hello Nicholas

注意,要访问函数的指针而不执行函数的话,必须去掉函数名后面的括号。因此上面例子给callSomeFunction()传递的是add10 和 getGreeting。而不是执行它们之后的结果。


可以从一个函数中返回一个函数,是一项极为有用的技术。例如,假设有一个对象数组(由对象组成的数组),我们想要根据对象中的某一个属性对数组进行排序,而传递给sort()方法的比较函数要接收两个参数,即要比较的值。可是,我们需要一种方式来告诉sort()按照哪个对象属性排序。要解决这个问题,可以定义一个函数,它接收一个属性名,然后根据属性名来创建一个匿名比较函数。下面就是这个函数的定义:

function creatComparisonFunction(propertyName){
    return function(obj1,obj2){
        var val1 = obj1[propertyName];   //细节,用方括号访问保存在变量propertyName中的属性,终于知道用处了
        var val2 = obj2[propertyName];

        if(val1<val2){
            return -1;
        }else if(val1>val2){
            return 1;
        }else{
            return 0;
        }
    };
}

看起来很复杂,其实就是嵌套了一个函数,而且内部函数前面加了一个return操作符。在内部函数接收到propertyName参数后,它会用方括号表示法来取得给定属性的值。取得这个对象属性后,就用这个属性进行比较。完成对象数组的排序。用法:

var data = [{name:"Zachary",age:28},{name:"Nicholas",age:29}];
data.sort(creatComparisonFunction("name"));
console.log(data[0].name);                    //Nicholas

data.sort(creatComparisonFunction("age"));
console.log(data[0].name);                    //Zachary



<h3>5.5.4函数内部的属性</h3>
在函数内部,有两个特殊的对象(函数对象中的对象):argumentsthis。arguments在第三章介绍过,是一个类数组对象,包含这个传入函数的所有参数。虽然arguments的主要用途是保存所有函数参数,但arguments对象还有一个名叫callee的属性,该属性是一个指针。arguments.callee( )可以指代这个函数本身。用处是可以消除函数的执行与函数名之间的耦合。还是那个阶乘的例子:

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*factorial(num-1);
    }
}

如果我把factorial这个函数名改为另一个函数的函数名:

var trueFactorial = factorial;
function factorial(){
     return 0;
}
console.log(trueFactorial(5))               // 0

这个时候trueFactorial()函数就出错了,要降低这种耦合,使用arguments.callee( )是最好方法。

function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*arguments.callee(num-1);  //即使factorial被改写,arguments.callee()仍代表当前函数。
    }
}

函数内部的另一个特殊对象是this,其行为与Java和C#中的this大致类似。换句话说,this引用的是函数据以执行的环境对象。当在网页的全局作用域中调用函数时,this对象引用的就是window。例子:

indow.color = "red";
var o = {color:"blue"};

function sayColor(){
     console.log(this.color);
}
sayColor();               //red
o.sayColor=sayColor;
o.sayColor();             //blue

函数sayColor( )首先是在全局作用域中定义的,它引用了this对象。但在调用它之前,并不确定是在哪个作用域中调用。当在全局作用域中调用sayColor( ),this引用的是全局对象,所以this.color = window.color。结果返回red。当把这个函数赋给对象o,并调用o.sayColor( )时,this引用的是对象o,因此this.color = o.color。结果返回blue。(亲测把sayColor()函数放在另一个函数中去调用,最后得到的结果还是window,再嵌多一层函数也还是window,说明this引用的不是作用域,而是函数据以执行的环境对象,还有一个原因是当你把函数嵌套进另一个函数的时候,返回是window的原因是你此时调用的实际上已经不是一个方法,而是一个函数。

ECMAScript5还规范化了另一个函数对象的属性caller。除了Opera早期版本不支持,其他浏览器IE,Firefox,Chrome,和Safari的所有版本及Opear9.6都支持caller属性。这个属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。例如:

function outer(){
    inner();
}
function inner(){
    console.log(inner.caller);     //会打印出outer()的源代码
}
outer();

以上代码会打印出outer( )的源代码,因为outer()调用了inner(),所以inner.caller就指向outer()。当然不能试图用inner.caller( )来调用outer。否则会陷入无限回调使代码出错。为了更松散的耦合,也可以通过arguments.callee.caller()来访问相同的信息。

function outer(){
    inner();
}
function inner(){
    console.log(arguments.callee.caller);     //会打印出outer()的源代码
}
outer();

arguments在严格模式下无法使用,arguments对象也有一个caller属性,但这个值永远是undefined。定义这个属性是为了分清arguments.caller和函数的caller属性,这个变化是出于增强这门语言的安全性。
严格模式还有一个限制:不能为函数的caller属性赋值,否则会导致错误。


<h3>5.5.5函数属性和方法</h3>
前面提过ECMAScript中函数是对象,所以函数就有属性和方法,上面介绍了一个caller。还有两个是lengthprototype
length属性表示函数希望接收的命名参数的个数。命名参数有1个,length的值就是1,参数有两个,length的值就是2。不解释。

作者说,在ECMAScript核心所定义的全部属性中,最耐人寻味的就要数prototype属性了(不止函数的prototype,其他对象的prototype属性也耐人寻味!)对与ECMAScript中的引用类型而言,prototype是保存他们所有实例方法的真正所在。换句话说,诸如valueOf(),toString()等方法实际上都是保存在了prototype属性名下。只不过是通过各自对象的实例访问到了。在创建自定义引用类型以及实现继承时,prototype属性的作用是极为重要的(第6章会详细介绍)。
在ECMAScript中,prototype属性是不可枚举的,因此无法用for-in无法发现prototype这个属性。(对property属性的讲解到此为止)

每个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的作用域中调用函数。实际上等于设置函数体内this对象的值。两个方法的区别只是在传入参数的方法上有区别而已。
首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例(就是普通数组),也可以是arguments对象。例子:

function sum(num1,num2){
    return num1+num2;
}

function callSum1(num1,num2){
    return sum.apply(this,arguments);
}

function callSum2(num1,num2){
    return sum.apply(this,[num1,num2]);
}
console.log(callSum1(10,10));          //20
console.log(callSum2(10,10));          //20

在上面这个例子中,callSum1( )在执行sum()函数时传入了 this 作为 this 值(???)(因为是在全局作用域中调用的,所以传入的就是window对象)和arguments对象,而callSum2传入的第二个参数则是数组型。这两个函数都会正常执行并返回正确结果。(这个例子只是为了说明两种传入参数的方式都可以,还没体现出apply()和call()的作用)

✎在严格模式下,未指定环境对象而用函数,则this值不会转型为window(严格模式下的this不会默认转型为window)除非明确把函数添加到某个对象或者调用apply()或call(),否则this值将是undefined

call()方法与apply()方法的作用相同,区别仅在于接收参数的方式不同。对于call()方法,第一个参数仍是this不变,变化的是其余参数都是直接传递给函数。换句话说,在使用call()方法时,参数都是逐个列举出来的。例子:

function sum(num1,num2){
    return num1+num2;
}

function callSum1(num1,num2){
    return sum.call(this,num1,num2);
}
console.log(callSum1(10,10));          // 20

在使用call()方法的情况下,callSum( )必须明确地传入每一个参数。结果与apply()没有什么不同。使用哪一种完全是看你觉得哪个更方便(MD以前还觉得这两个方法多深奥ZZ)
apply()和call()的真正强大之处,是能够扩充函数赖以运行的作用域。例子:

window.color = "red";
var o = {color:"blue"};

function sayColor(){
    console.log(this.color)
}

sayColor();                   //red
sayColor.call(window);        //red
sayColor.call(o);             //blue

第一次调用sayColor()时在全局作用域中调用它,会显示“red”——因为对this.color的求值会转换成对window.color的求值。当运行sayColor。call(o)时,函数的执行环境对象就不一样了,因此此时函数体内的this对象指向了O,于是结果显示“blue”。
使用call()和apply()的最大好处,就是对象不需要与方法有任何耦合关系(不用为了在这个对象内调用方法而把函数写进对象中)。在书中前面的第一个例子中(笔记没有),为了在对象o中调用sayColor( ),要把这个函数写进对象o中,再通过o调用,而在这里的例子中,就不需要那么多步骤了。
ECMAScript5又定义了一个方法:bind()(所以函数对象的方法有三个)。这个方法会创建一个函数的实例(会创建一个函数),其this值会被绑定到传给bind()函数的值。例子:

window.color = "red";
var o = {color:"blue"};
function sayColor(){
    console.log(this.color)
}

var objSayColor = sayColor.bind(o);
objSayColor();

在这里,sayColor()调用bind()并传入对象o,创建了objSayColor()函数。objSayColor()的this值等于o。因此无论在哪个环境对象中调用这个函数,都会看到“blue”。这种技巧的优点参考第22章。
因为是ECMAScrip5才有的方法,所以可以使用bind() 的浏览器有: IE9+,Firefox4+,Safari 5.1+,Opera 12+和Chrome。(兼容性挺低的)

最后说每个引用类型值都有继承的,都会被重写的toLocaleString()和toString(),valueOf(),这三个方法都始终返回函数的代码。返回代码的格式根据浏览器的不同会有差异。因为有返回值差异,所以返回的结果无法被用来实现任何功能,不过这些信息在调试代码时很有用。

<h2>5.6基本包装类型</h2>
读到这里又刷新了三观。这里说Boolean、Number、String是3个ECMAScript提供的特殊引用类型。这些类型与本章介绍的其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。实际上,每创建一个基本类型的时候,后台就会创建一个对应的基本包装类型对象(WTF???),从而让我们能够调用一些方法来操作这些数据(这解释了为什么三种基本类型值也有自带的属性和方法)。例子,我们可以这样使用基本类型值的方法:

var s1 = "some text";
var s2 = s1.subString(2);

可以看到我们可以调用String类型自带的方法,但如下面的例子,我们不能在运行时为基本类型值添加属性和方法

var s1 = "some text";
s1.color = "red";
console.log(s1.color);     //undefined

我们知道,基本类型不是对象,因而从逻辑上它不应该有方法(尽管如我们所愿,它有方法)。其实,为了让我们能用方法操作基本类型值,后台已经自动完成了一系列的处理(注意后面是原理):当第一段代码的第二行(var s2 = s1.subString(2); )访问s1时,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理:
(1)创建String类型的一个实例
(2)在实例上调用指定的方法;
(3)销毁这个实例。
以上三个步骤用代码表示就是:

var s1 = new String("some txt");
var s2 = s1.subString(2);    //把结果放回给另一个变量
s1 = null;                   //销毁这个对象

上面的步骤也适用于Boolean,Number类型。这也就解释了为什么不能给基本类型值添加属性和方法,因为基本类型值对象在调用完方法后就会被后台自动销毁。

引用类型和基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中(离开了作用域后可能就会被标记,然后被垃圾收集机制回收)。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁(亲测手动用new操作符创建的基本类型对象,可以添加属性和方法。)所以我们无法为基本类型值添加属性和方法。
当然如果没有必要的话,就不要显示地调用Boolean和Number、String来创建基本包装类型对象(不要var s1 = new String("some txt")这样写)。因为这样容易让人分不清自己是在处理基本类型还是引用类型的值。

Object构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。例如:

var obj = new Object("some text");
console.log(obj instanceof String);   //true
console.log(obj instanceof Object);   //true

就是把字符串传给Object构造函数,就会创建String的实例,传入数值参数就会得到Number实例,传入布尔值参数就会得到Boolean实例,但是你检测它是以上基本类型值会返回true,检测是不是Object也会返回true。
下面会讲每个基本包装类型提供的操作相应值的便捷方法。

<h3>5.6.1Boolean类型</h3>
这节的重点是理解基本类型的布尔值与Boolean对象之间的区别。当然,作者的建议是永远不要使用Boolean对象,这节就当冷知识吧。

创建Boolean对象可以像下面这样调用Boolean构造函数并传入true或false值。

var booleanObject = new Boolean(false)

Boolean类型的实例重写了的valueOf( )方法会返回基本类型的true和false,重写了的toString( )会返回字符串型的“true”和“false”。
书中前面提到过,但是当时不懂有什么用就没记的一段话:布尔表达式中的所有对象都会被转换为true。例子:

var falseObject = new Boolean(false);
var result = falseObject && true;
console.log(result);               //true

在这个例子中我们用false值创建了Boolean对象的实例falseObject,并对这个对象与true进行&&运算,却发现返回到结果是true。因为,示例中的代码是对falseObject而不是对它的值(false)求值。而布尔表达式中的所有对象都会被转换为true,结果true&&true当然就等于true了。

<h3>5.6.2Number类型</h3>
Number类型也重写了valueOf()、toLocaleString()、toString()。valueOf()返回这个对象表示的基本类型的数值(亲测就是一个数)。toLocaleString()、toString()返回字符串类型的数值。
Number的toString()方法可以传递一个表示基数的参数,参数是几,就按几进制的数字的字符串形式返回。

var num = 10;
console.log(num.toString(2))     //"1010"

注意返回的是字符串形式的。


上面的几个是继承的方法,Number类型提供了一些可以将数值格式化的方法。

  • toFixed():按照指定的小数位返回数字的字符串表示,且支持四舍五入。比如25.005用toFixed(2),会变成“25.01”。
  • toExponential(():返回以指数表示法表示的数值的字符串形式。接收的参数也是指定输出结果中的小数位数。
var num = 10.005;
console.log(num.toFixed(2))     //"10.01"

var num = 10;
console.log(num.toExponential(1))//"1.0e+1"

<h3>5.6.3String类型</h3>
String对象继承的valueOf()、toLocaleString()、toString()返回的都是这个对象表示的基本字符串值。
String类型的每个实例都有一个length属性,表示字符串中有多少个字符(亲测中文也是有多少个字长度就是几)。书中说了,即使字符串中包含双字节字符(不是占一个字节的ASCII字符,每个字符也仍然算一个字符)。

String类型提供的方法很多,看到都怕。
<h4>1.字符方法</h4>
两个用于访问字符串中特定位置字符的方法:charAt( )charCodeAt( )。两个方法都接收一个参数,即基于0的字符位置。两个方法的区别是charAt( )返回的是给定位置的那个字符。charCodeAt( )返回的是那个字符的字符编码。例子:

var stringValue = "hello World";
console.log(stringValue.charAt(1));     // "e"
console.log(stringValue.charCodeAt(1)); // "101"  是小写字母e的字符编码

ECMAScript5定义了另一个访问个别字符的方法,可以用方括号加数字索引来访问字符串中的特定字符。例子:

var stringValue = "hello World";
console.log(stringValue[1]);     //"e"

类似于把字符串当成一个“数组”,按位置取出所在位置的字符。亲测可以直接在字符串后面加方括号,例子:

console.log("sdsdsdsd"[2]);      //"s"  66666

支持这个语法的浏览器有IE8,Firefox,Safari,Opera 和Chrome所有版本的支持。冷知识:在IE7及更早版本使用这种语法会返回undefined值,尽管不是特殊的undefined值(不是很懂最后一句的意思,我以为返回的那个undefined是字符串型的,但是不是)

<h4>2.字符串操作方法</h4>
操作字符串的方法有几个,第一个是concat( ),可以将一或多个字符串拼接起来,参数就是要添加进去的字符串,参数数量不限。返回得到的新字符串。数组类型也有这个方法,不解释。不过在实践中使用最多的是加号操作符(+)(MDZZ)。
ECMAScript还提供了三个基于子字符串创建新字符串的方法:slice()、substr()、substring()。三个方法都返回被操作字符串的一个子字符串。三个方法的第一个参数都是指定子字符串的开始位置。slice()和substring()的第二个参数指定的是子字符最后一个字符后面的位置(忽略结尾那个位置)。而substr() 的第二个参数指定的则是返回字符的个数。如果没有第二个参数,则默认将字符串的长度作为结束的位置。(太乱实际用再百度)。与concat()方法一样,这三个方法也不会修改本身的那个字符串。对原始字符串无任何影响。例子:

var stringValue = "Hello World";
console.log(stringValue.slice(3));               //"lo world"
console.log(stringValue.substring(3));           //"lo World"
console.log(stringValue.substr(3));              //"lo World"
console.log(stringValue.slice(3,7));             //"lo W"
console.log(stringValue.substring(3,7));         //"lo W"
console.log(stringValue.substr(3,7));            //"lo Worl"   substr()第二个参数表示要截取的字符个数

看起来三个方法作用很容易理解,但是传递给这些方法的参数是负数的情况下,他们的行为就不尽相同了。其中,slice()会将传入的负值与字符串的长度相加,substr()将负的第一个参数加上字符串的长度,而将第二个负的参数转换为0.最后,substring()方法会将所有负值参数都转换为0.很恐怖,不想举例子了,实践中没事不要用负数就行。需要再看书。
<h4>3.字符串位置方法</h4>
有两个可以从字符串中查找子字符串的方法:indexOf( )和lastIndexOf( )。这两个方法都是从一个字符串中搜索给定的子字符串,然后返回子字符串的位置。查找不到就返回-1.两个方法的区别是一个从头向后查询,一个从后向前查询。例子:

var stringValue = "hello world";
console.log(stringValue.indexOf("o"));    //4
console.log(stringValue.lastIndexOf("o")) //7

这两个方法都接收第二个参数,表示从字符串哪个位置开始搜索。换句话说,indexOf()从该参数指定的位置向后搜索,忽略之前的所有字符;而lastIndexOf()则会从指定的位置向前搜索,忽略该位置之后的所有字符(为什么在讲第二次的时候才讲这么清楚)。例子:

var stringValue = "hello world";
console.log(stringValue.indexOf("o",6));   //7
console.log(stringValue.lastIndexOf("o",6))//4

indexOf( )从位置6(字母“w”)开始向后搜索,在位置7找到“o”。lastIndexOf( )从位置6开始向前搜索,找到的是“hello”中的“o”,所以返回4。

我们可以在使用第二个参数的情况下,通过循环把所有匹配的子字符串找出来,例子:

var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = new Array();
var pos = stringValue.indexOf("e");

while(pos>-1){
    positions.push(pos);
    pos = stringValue.indexOf("e",pos+1);
}
console.log(positions);                    //[3, 24, 32, 35, 52]

<h4>4.trim()方法</h4>
ECMAScript5定义了trim()方法,该方法会创建一个字符串副本删除前置和后置的所有空格,然后返回结果。
支持这个方法的浏览器有:IE9+,Firefox 3.5+,Safari 5+,Opera 10.5+和Chrome。此外,Firefox 3.5+,Safari 5+和Chrome 8+还支持非标准的trimLeft( )和trimRight( ) ,分别用于删除字符串开头和末尾的空格。

<h4>5.字符串大小写转换方法</h4>
toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。为什么会有toLocaleLowerCase()、toLocaleUpperCase()这两个方法是因为有些地区比如土耳其语回味iUnicode大小写转换应用特殊的规则也是醉了,所以用这两个针对地区的方法来保证正确的转换。
<h4>6.字符串的模式匹配方法</h4>
正则看得头大
<h4>7.localCompare方法</h4>
tolocalCompare()方法就是用字符串跟方法的字符串参数比较,如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1),如果字符串等于字符串参数,则返回0;如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是1)。
而且localCompare()方法有个与众不同的地方,大小写是否区分要视使用方法的地区不同而定。比如美国以英语作为ECMAScript实现的标准语言,因此localCompare()就是区分大小写的,则大写字母在字母表中会排在小写字母前面。所以是否区分大小写要根据地区而定。(亲测中国地区区分大小写且大写字母在小写字母之前

<h4>8.fromCharCode()方法</h4>
String构造函数(是构造函数的方法不是普通字符串的方法)本身还有一个静态方法:fromCharCode( )。这个方法可以接收一或多个字符编码,将他们转换为字符串。例子:

<h4>9.HTML方法</h4>
作者建议尽量不用这些方法,因为他们创建的标记通常无法表达语义(不懂)。方法有12个,需要再查书吧。

<h3>5.7单体内置对象</h3>
ECMA-262对内置对象的定义是:“由ECMAScript实现提供的,不依赖宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了,所以开发人员不用显示地实例化内置对象,因为他们已经实例化了。”前面介绍的Object、Array和String等都是内置对象。ECMA-262还定义了两个单体内置对象:Global和Math

<h4>5.7.1Global对象</h4>
Global(全局)对象是ECMAScript中最特别的一个对象,因为你不管从什么角度上看,这个对象都是不存在的(???)。ECMAScript中的Global对象在某种意义上是作为一个“兜底儿对象”。换句话说,不属于任何对象的属性和方法,都是global对象的属性和方法。事实上,没有全局变量和全局函数,因为所有在全局作用域中定义的属性和函数,都是Global对象的属性(只是属性,函数也是Global对象的属性)。本书前面介绍过的那些函数,比如isNan(),isFinite()、parseInt()以及parseFloat(),都是Global对象的方法(array,object等类型对象的方法当然不是Global对象的,是array、object等这些对象的,不要混淆)。除此之外,Global还有其他一些方法。下面介绍这几个方法:
<h4>1.URI编码方法</h4>
Global对象的encodeURI()和encodeURIComponent()方法可以对URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。
有效的URI不能包含某些字符,例如空格,而这两个URI编码方法就会对URI进行编码,用特殊的UTF-8字符替换所有无效的字符,让浏览器能够接受和理解。encodeURI()和encodeURIComponent()的第一个区别是,encodeURI()只会把URI中无效的字符替换掉,encodeURIComponent()会把正确的特殊符号例如冒号、正斜杠、问号和井号也给替换掉。
第二个区别是,encodeURI()用于整个URI,而encodeURIComponent()用于对URI中的某一段进行编码。例子:

var uri = "http://www.wrow.com/illegal  value.html#start";

console.log(encodeURI(uri));              //http://www.wrow.com/illegal%20%20value.html#start

console.log(encodeURIComponent(uri));       //http%3A%2F%2Fwww.wrow.com%2Fillegal%20%20value.html%23start

第二个方法中连http://都被转换了字符导致无法访问,说明了一、encodeURIComponent()只适合编码部分URI;二、encodeURIComponent()会把一切正确,不正确的特殊符号都进行UTF-8字符编码

有编码的函数,就有解码的函数,与这两个函数对应的函数是decodeURI()和decodeURIComponent()。其中,decodeURI()只能对非法字符进行解码,decodeURIComponent()可以对所有被编译的符号进行解码。例如,decodeURI()可以将%20替换成空格,但不能对%23做任何处理,因为%23表示井字号(#),而井字号是合法符号。但是decodeURIComponent()就%20和%23都可以解码。

<h4>2.eval()方法</h4>
最后一个Global对象的方法,大概是整个ECMAScript语言中最强大的一个方法:eval()(见仁见智,有些人觉得这个方法弊端多多)。eval()就像一个完整的ECMAScript解析器,它只接收一个参数,即要执行的ECMAScript(或JavaScript)字符串。例子:

eval("alert('hi')");

以上代码等价于下面这行(so what??):

alert('hi');

当解析器发现代码中调用eval()方法时,它会将传入的参数当作实际的ECMAScript语句来解析,然后把执行结果插入到原来位置。通过eval()方法执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过eval()执行的代码可以引用在包含环境中定义的变量。例子:

var msg = "hello world";
eval("alert(msg)");

可见,变量msg是在eval()调用的环境之外定义的,但其中调用的alert()仍然能够显示“hello world”。这是因为上面第二行代码最终被替换成了一行真正的代码(so?那干嘛要加个eval(),直接写进执行环境不就好了吗)。同样地,我们也可以在eval()调用中定义一个函数,然后在外部代码中引用这个函数:

eval("function sayHi(){ alert('Hi'); }");
sayHi();

显然,函数sayHi()是在eval()内部定义的。但由于对eval()的调用最终会被替换成定义函数的实际代码,因此可以在下一行的外部代码中调用sayHi( )。

在eval()中创建的仍和变量或函数都不会被提升,原理:因为在解析代码的时候,他们被包含在一个字符串中,它们只在eval()执行的时候创建。

严格模式下,在外部访问不到eval()中创建的任何变量或函数,因此前面的那个例子就会导致错误。在严格模式下,为eval赋值也会导致错误:

"use strict";
eval = "hi";   //causes error

<h4>3.Global对象的属性</h4>
前面说过了,没有明确对象的属性,最后都是Global的属性,例如,特殊的值undefined、NaN以及Infinity等。此外,所有原生引用类型的构造函数,像Object,Function也都是Global对象的属性。书中列出了所有Global对象的所有属性。在P133.

ECMAScript 5明确禁止给undefined、NaN和Infinity赋值,这样做即使在非严格模式下也会导致错误。
<h4>4.window对象</h4>
ECMAScript没有明确告诉我们如何直接访问Global对象,但Web浏览器都是将这个全局对象作为window对象的一部分加以实现的(在浏览器中,Global对象是window对象的一部分,注意范围:浏览器中,window对象的一部分。)例子:

var color = "red";

function sayColor(){
     alert(window.color);
}
window.sayColor();     //"red"

在这里我们定义了一个名为color的全局变量和一个sayColor()全局函数。在sayColor()内部,我们用window.color来访问变量,前面我们说过,全局环境中定义的变量和函数,实际上都是Global的属性,所以window.color等价于Global.color。以此说明,全局变量是window对象的属性。然后,又使用window.color来直接通过window.sayColor( )直接通过window对象调用这个函数。

✎JavaScript中的window对象除了扮演ECMAScript规定的Global对象的角色外,还承担了很多任务,所以说Global对象这是window对象的一部分。第8章在讨论浏览器对象模型时将详细介绍window对象。

另一种取得Global对象的方法是使用以下代码:

var global = function(){
     return this;          // 亲测return出来的是 window
}();

这是个立即调用的函数表达式,返回this的值。如前所述,在没有给函数明确指定this值的情况下,this值等于Global对象。而像这样通过简单地返回this来取得Global对象,在任何执行环境下都是可行的。(因为是立即执行函数的原因?)

<h4>5.7.2Math对象</h4>
不想写了这个,就是Math下的各种方法。可以直接在书中查阅。

推荐阅读更多精彩内容