《JavaScript高级程序设计》读书笔记(二):引用类型

来源:互联网 发布:大漠骑兵网络 编辑:程序博客网 时间:2024/05/17 08:07

一、引用类型

1.Object类型

属性:

Constructor:对创建对象的函数的引用(指针)。对于Object类,该指针指向原始的object()函数。

Prototype:对该对象的对象原型的引用。对于所有的类,它默认返回Object对象的一个实例。


方法:

hasOwnProperty(property):判断对象是否有某个特定的属性。必须用字符串指定该属性(例如,o.hasOwnProperty(”name”))。

isPrototypeOf(object):判断该对象是否为另一个对象的原型。

propertyIsEnumerable(property):判断给定的属性是否可以用for…in语句进行枚举。

toString():返回对象的原始字符串表示。对于Object类,ECMA-262没有定义这个值,所以不同的ECMAScriipt实现具有不同的值。

valueOf():返回最适合该对象的原值。对于许多类,该方法返回的值都与toString()的返回值相同。


创建 Object实例的方式有两种。第一种是使用new操作符后跟Object构造函数 ,如:

var person = new Object();

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。 如:

var person = {    name : "Nicholas",    age : 29

}; 

另外,使用对象字面量语法时,如果留空其花括号,则可以定义只包含默认属性和方法的对象,如下所示:

var person = {}; //与new Object()相同 


一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法。不过,在 JavaScript 也可以使用方括号表示法来访问对象的属性。在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中,如下面的例子所示。

alert(person["name"]); //"Nicholas"

alert(person.name); //"Nicholas" 

从功能上看,这两种访问对象属性的方法没有任何区别。但方括号语法的主要优点是可以通过变量来访问属性,例如:

var propertyName = "name";alert(person[propertyName]); //"Nicholas" 


2.Array类型

1.创建数组的两种方式

第一种是使用 Array 构造函数 :

var colors = new Array(20);

 var colors = new Array("red", "blue", "green");

var names = new Array("Greg"); // 创建一个包含 1 项,即字符串"Greg"的数组 

(另外,在使用 Array 构造函数时也可以省略 new 操作符。 )


第二种基本方式是使用数组字面量表示法:

var colors = ["red", "blue", "green"]; //创建一个包含3个字符串的数组

2.length属性

数组的 length 属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。 

var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组

colors.length = 2;
alert(colors[2]); //undefined 

var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组

colors.length = 4;
alert(colors[3]); //undefined 

var colors = ["red", "blue", "green"]; //创建一个包含3个字符串的数组

colors[99] = "black"; //(在位置99)添加一种颜色

alert(colors.length); // 100

在这个例子中,我们向 colors 数组的位置99插入了一个值,结果数组新长度(length)就是100(99+1)。而位置3到位置98实际上都是不存在的,所以访问它们都将返回undefined。 


数组最多可以包含4 294 967 295个项,这几乎已经能够满足任何编程需求了。如果想添加的项数超过这个上限值,就会发生异常。而创建一个初始大小与这个上限值接近的数组,则可能会导致运行时间超长的脚本错误。 

3.检测数组

对于一个网页,或者一个全局作用域而言,使用instanceof 操作符就能得到满意的结果:

if (value instanceof Array){//对数组执行某些操作}

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array 构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

为了解决这个问题,ECMAScript 5新增了 Array.isArray()方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。这个方法的用法如下。

if (Array.isArray(value)){//对数组执行某些操作}


4.shift和unshift

由于 push()是向数组末端添加项的方法,因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是 shift(),它能够移 7除数组中的第一个项并返回该项,同时将数组长度减 1。结合使用 shift()和 push()方法,可以像使用队列一样使用数组。 

ECMAScript 还为数组提供了一个 unshift()方法。顾名思义,unshift()与 shift()的用途相反:它能在数组前端添加任意个项并返回新数组的长度。因此,同时使用 unshift()和 pop()方法,可以 12从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项。

5.concat和slice

var colors = ["red", "green", "blue"];    var colors2 = colors.concat("yellow", ["black", "brown"]);
    alert(colors);     //red,green,blue    alert(colors2);    //red,green,blue,yellow,black,brown

var colors = ["red", "green", "blue", "yellow", "purple"];var colors2 = colors.slice(1);var colors3 = colors.slice(1,4);
alert(colors2);   //green,blue,yellow,purplealert(colors3);   //green,blue,yellow

都不会影响原来的数组。

6.splice

splice()的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种。

删除:可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。

插入:可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置2开始插入字符串"red""green"

替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置2的项,然后再从位置2开始插入字符串"red""green"。 

7.迭代方法

every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true

filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true

   以上方法都不会修改数组中的包含的值。

 var numbers = [1,2,3,4,5,4,3,2,1];
     var everyResult = numbers.every(function(item, index, array){         return (item > 2);

});

alert(everyResult); //false

     var someResult = numbers.some(function(item, index, array){         return (item > 2);

});
alert(someResult); //true 

var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){    return (item > 2);

});

alert(filterResult);
//[3,4,5,4,3]

var numbers = [1,2,3,4,5,4,3,2,1];

    var mapResult = numbers.map(function(item, index, array){        return item * 2;
    });    alert(mapResult);  //[2,4,6,8,10,8,6,4,2]

var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){

}); 

8.归并方法

reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。 

reduce()reduceRight()的函数接收4个参数:前一个值、当前值、项的索引和数组对象。 

var values = [1,2,3,4,5];var sum = values.reduce(function(prev, cur, index, array){
    return prev + cur;});
alert(sum); //15

使用 reduce()还是reduceRight(),主要取决于要从哪头开始遍历数组。除此之外,它们完全相同。 


3.RegEx类型

还需要再深入学习。

4.Function类型

声明函数的三种方式:

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

}

    var sum = function(num1, num2){        return num1 + num2;

}; 


最后一种定义函数的方式是使用 Function 构造函数。Function 构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。来看下面的例子:

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

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

1.没有重载

  function addSomeNumber(num){        return num + 100;

}

    function addSomeNumber(num) {        return num + 200;
    }    var result = addSomeNumber(100); //300

显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。 

2.函数声明与函数表达式

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

以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript 引擎也能把函数声明提升到顶部。如果像下面例子所示的,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。

alert(sum(10,10));
var sum = function(num1, num2){
    return num1 + num2;};

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量 sum 中不会保存有对函数的引用;而且,由于第一行代码就会导致“unexpected identifier”(意外标识符)错误,实际上也不会执行到下一行。

除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。 

3.作为值的函数

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

    function createComparisonFunction(propertyName) {
        return function(object1, object2){            var value1 = object1[propertyName];            var value2 = object2[propertyName];
            if (value1 < value2){                return -1;
            } else if (value1 > value2){                return 1;

}};

4.函数内部属性

1.arguments

arguments是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 

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

}} 

这样可以解除函数与函数名的耦合


function outer(){        inner();
    }function inner(){
        alert(arguments.callee.caller);

}

outer(); 

以上代码会导致警告框中显示 outer()函数的源代码 。

2.this

this引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时,this 对象引用的就是 window) 。

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

函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的 sayColor()函数与 o.sayColor()指向的仍然是同一个函数。 

5.函数的属性和方法

每个函数都包含两个属性:length 和 prototype。

length 属性表示函数希望接收的命名参数的个数, 如下面的例子所示。

function sayName(name){    alert(name);

}

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

}

function sayHi(){    alert("hi");

}

alert(sayName.length);      //1alert(sum.length);          //2alert(sayHi.length);        //0

对于ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。 换句话说,诸如toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。 

每个函数都包含两个非继承而来的方法: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]);

call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。 

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

}

function callSum(num1, num2){
    return sum.call(this, num1, num2);
}

传递参数并非 apply()call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。 

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

}

sayColor(); //red

sayColor.call(this); //redsayColor.call(window); //redsayColor.call(o);//blue


ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。例如:

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

}

var objectSayColor = sayColor.bind(o);objectSayColor();    //blue

5.基本包装类型

Boolean,Number,String

6.单体内置对象

ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了。” 

意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如 Object、Array 和 String。

ECMA-262 还定义了两个单体内置对象:Global 和 Math。 

1.Global

ECMAScript 中的 Global 对象在某种意义上是作为一个终极的“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。本书前面介绍过的那些函数,诸如 isNaN()、isFinite()、parseInt()以及 parseFloat(),实际上全都是 Global对象的方法。

ECMAScript 虽然没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了 window对象的属性:

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

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

    var global = function(){        return this;

}();

以上代码创建了一个立即调用的函数表达式,返回 this 的值。如前所述,在没有给函数明确指定this 值的情况下(无论是通过将函数添加为对象的方法,还是通过调用 call()或 apply()),this值等于 Global 对象。而像这样通过简单地返回 this 来取得 Global 对象,在任何执行环境下都是可行的。 

2.Math

提供一些方法来完成复杂的数学计算任务。


1 0
原创粉丝点击