JavaScript对象
1 JavaScript对象的定义
1.1 对象是JavaScript的基本数据类型
- 对象是一种复合值, 它将很多值(原始值或者其他对对象)聚合在一起, 可通过名字访问这些值
-
对象可看作无序属性的集合,其属性可以包含基本值、对象或者函数,严格来说对象是一组没有特定顺序的值
-
对象的每个属性或方法都有一个名字,而每个名字都映射到一个值(类似散列表、字典、关联数组等),一组 名/值 对,其中值可以是数据或函数)
- 对象除了可以拥有自身的属性外,还可以通过原型对象继承属性,对象的方法通常都是继承的属性,原型式继承是JavaScript语言的核心特征
1.2 JavaScript 的对象是动态的
- 对象的属性可以新增,也可以删除的
1.3 对象是可变的,通过引用来操作
-
多个变量指向同一个对象,其中任何一个变量操作改变对象,这种改变都会反应到其他变量,即改变的对象本身而不是对象的副本
-
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate )它的属性
1.4 对象的属性、属性特性与对象特性
- 属性包括名字和值
- 属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性
- 值可以是任意JavaScript值, 或者(在ECMAScript 5中)可以是一个getter或setter函数(或两者都有)
- 除了名字和值之外,每个属性还有一些与之相关的值,称为 属性特性 (property attribute)
-
可写(writable attribute),表明是否可以设置该属性的值
-
可枚举(enumerable attribute),表明是否可以通过于or/in循环返回该属性
-
可配置(configurable attribute),表明是否可以删除或修改该属性
-
- 除了包含属性之外,每个对象还拥有三个相关的对象特性(object attribute)
-
对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象
-
对象的类(class)是一个标识对象类型的字符串
-
对象的扩展标记(extensible flag)指明了(在ECMAScript 5中)是否可以向该对 象添加新属性
-
1.5 三类JavaScript对象和两类属性
-
内置对象(native object)是由ECMAScript规范定义的对象或类。 例如,数组、 函数、 日期和正则表达式都是内置对象
-
宿主对象(host object)是由JavaScript解释器所嵌入的宿主环境(比如Web浏览器)定义的
-
客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象
-
既然宿主环境定义的方法可以当成普通的JavaScript函数对象, 那么宿主对象也可以当成内置对象
-
-
自定义对象(user-defined object)是由运行中的JavaScript代码创建的对象
-
自有属性(own property)是直接在对象中定义的属性
-
继承属性(inherited property)是在对象的原型对象中定义的属性
2 JavaScript对象的创建
- 每个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型,也可以是自定义的类型
- 可以通过对象字面量、 关键字new和(ECMAScript 5中的)Object.create()函数来创建对象
var o1 = {};//字面量创建对象 var o2 = new Object();//new关键字创建对象 var o3 = Object.create();//Object.create()静态方法创建方法
2.1 对象字面量创建对象
- 对象直接量是由若干名/值对组成的映射表
- 名/值对中间用冒号分隔
- 名/值对之间用逗号分隔
- 整个映射表用花括号括起来
- 属性名可以是JavaScript标识符也可以是字符串直接量(包括空字符串)
- 属性的值可以是任意类型的JavaScript表达式,表达式的值(可以是原始值也可以是对象值)就是这个属性的值
var empty={}; //没有任何属性的对象 var point= { x:o, y:o };//两个属性 var point2 = { x:point.x, y:point.y+1 };//更复杂的属性值 var book= { "main title":"JavaScript”,//属性名字里有空格,必须用字符串表示 "sub-title":"The Definitive Guide",//属性名字里有连字符,必须用字符串表示 "for":"all audiences",// ”for”是保留字,因此必须用引号 author:{//这个属性的值是一个对象 firstName:"David",//注意,这里的属性名都没有引号 surname:"Flanagan" }//最后一个属性不需要逗号 }
- 对象直接量是一个表达式, 这个表达式的每次运算都创建井初始化一个新的对象
- 每次计算对象直接量的时候,也都会计算它的每个属性的值
- 也就是说, 如果在一个重复调用的函数中的循环体内使用了对象直接量, 它将创建很多新对象, 井且每次创建的对象的属性值也有可能不同
2.2 new关键字创建对象
- new运算符创建并初始化一个新对象
- 关键字new后跟随一个函数调用。这里的函数称做构造函数(constructor), 构造函数用以初始化一个新创建的对象
- JavaScript语言核心中的原始类型都包含内置构造函数
- 除了这些内置构造函数,用自定义构造函数来初始化新对象也是非常常见的。
var a = new Array();//创建一个空数组, 和[]一样 var d = new Date(); //创建一个表示当前时间的Date对象 var r = new RegExp(”js");//创建一个可以进行模式匹配的EegExp对象 var selfF = new SelfFunc();//创建一个自定义构造函数的对象
2.3 Object.create()创建对象
-
Object.create()的方法,包含两个参数
-
第一个参数是这个对象的原型
-
第二个是可选参数,用以对对象的属性进行进一步描述
-
- Object.create()个静态函数,而不是提供给某个对象调用的方法。使用它的方站法很简单,只须传入所需的原型对象即可
var 01 = Object.create({x:1, y:2});//o1继承了属性x和y var 02 = Object.create(null); //02不继承任何属性和方法,包括顶级对象Object.prototype var 03 = Object.create(Object.prototype);//03和{}和new Object()一样
2.4 采用构造函数模式与原型模式的组合来创建对象的类
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
- 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
- 组合模式还支持向构造函数传递参数
//构造函数:每个实例都会分配对应的地址,因此修改实例只会修改对应实例分配的地址中的值 function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } //原型prototype:仅首次分配地址,所有实例都共享原型上的方法与属性 Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); //仅影响person1 alert(person1.friends);//"Shelby,Count,Van" alert(person2.friends);//"Shelby,Count" alert(person1.friends === person2.friends);//false,构造函数定义的属性,对每个实例都不同 alert(person1.sayName === person2.sayName);//true,原型上定义的方法,由所有实例实例共享
-
上例中,实例属性都是在构造函数中定义的,而由所有实例共享的属性 constructor 和方法 sayName()则是在原型中定义的
-
修改 person1.friends(向其中添加一个新字符串),并不会影响到 person2.friends,因为它们分别引用了不同的数组
-
构造函数与原型组合创建对象的模式,是目前使用最广泛、认同度最高的一种创建自定义类型的方法(定义引用类型的一种默认模式)
3 继承
- 继承一般有两种继承方式:接口继承和实现继承
- 接口继承只继承方法签名(声明),实现继承则继承实际的方法
- 由于JavaScript 函数没有签名,所以无法实现接口继承,只支持实现继承
- JavaScript 的实现继承主要是依靠原型链来实现的
3.1 原型链
- 原型链作为实现继承的主要方法。其基本思想是利用原型,让一个引用类型继承另一个引用类型的属性和方法
- 构造函数、原型和实例的关系
- 每个构造函数都有一个原型对象
- 每个原型对象都有一个指向构造函数的指针
- 每个实例都有一个指向原型对象的内部指针
- 原型链的基本概念
- 让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针
- 相应地,另一个原型中也包含着一个指向另一个构造函数的指针
- 如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条
- 原型链继承实现的本质是重写子类的原型对象,即将父类的实例对象赋值给子类的原型对象
3.1.1 原型链的基本模式
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //继承了SuperType :将SuperType的实例赋值给SubType的原型 SubType.prototype = new SuperType(); //可理解为:将父类的实例对象赋值给子类的原型对象
//注意:
//1 子类定义新方法或重写父类的方法,应在上条语句后进行,即在确立原型继承后。
//2 只能采用如下的在子类原型方式定义子类新方法或重写父类的方法,不能使用原型字面量方式,即
//SubType.prototype = {
//getSubValue:function (){return this.subproperty;},
//getSuperValue:function(){this.prototype = false;}
//}
//3 采用2的方式,字面量原型将覆盖子类的原型,等于再次更新了SuType.prototype,导致SubType.prototype = new SuperType()被覆写,继承失效
SubType.prototype.getSubValue = function (){//子类增加新方法,只能采用这种方式
return this.subproperty;
};
var instance = new SubType(); alert(instance.getSuperValue());//true,说明SubType继承了SuperType的方法
- 以上代码定义了两个类型:SuperType 和 SubType。每个类型分别有一个属性和一个方法
- 主要区别是 SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋给SubType.prototype 实现的
- 实现的本质是重写SubType的原型对象
- 用新类型的实例替代原型对象(SubType.prototype = new SuperType() )
- 原存于 SuperType 的实例中的所有属性和方法,现也存于SubType.prototype 中了
- 确立了继承关系后,给 SubType.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方法的基础上又添加了一个新方法
3.1.2 原型链继承的问题
- 包含引用类型值的原型属性会被所有实例共享
- 这就是为何要在构造函数中而不是在原型对象中定义属性的原因
- 在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就变成了现在的原型属性了。
-
创建子类型的实例时,不能向超类型的构造函数中传递参数。应该说无法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
-
有鉴于此,实践中很少会单独使用原型链。
3.2 借用构造函数继承
- 借用构造函数(constructor stealing,有时也叫做伪造对象或经典继承)的技术,可解决原型中包含引用类型值所带来问题
- 基本思想是在子类型构造函数的内部调用超类型构造函数
- 因为函数只不过是在特定环境中执行代码的对象,所以通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数
- 借用构造函数的方法都在构造函数中定义,也存在以下问题:
- 函数无法复用
- 超类型的原型中定义的方法,对子类型而言也是不可见,结果所有类型都只能使用构造函数模式
- 这些问题的存在,导致借用构造函数的技术几乎无法单独使用
function SuperType(name){
this.name =name;
this.colors = ["red", "blue", "green"]; } function SubType(){ //继承了SuperType
//1 通过使用call()方法(或apply()方法),在(未来将要)新创建的SubType 实例的环境下调用了SuperType 构造函数
//2 在新SubType 对象上执行SuperType()函数(即父类构造函数)中定义的所有对象初始化代码,SubType 的每个实例便拥有自己的colors 属性副本了
//3 也实现了传参
SuperType.call(this,'Nicoles');
}
//下面创建的两个实例,各自拥有独立对应的colors属性 var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black"
alter(instance1.name);//Nicole
var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
alter(instance2.name);//Nicole
3.3 组合继承
- 组合继承(combination inheritance,有时也叫伪经典继承),指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
- 组合继承的思路
- 原型链实现对原型属性和方法的继承,在原型上定义的方法所有实例共享,可实现函数的复用
- 借用构造函数来实现对实例属性的继承,构造函数内定义的属性,可使每个实例独享,互不干扰
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name, age){ //继承属性:借用构造函数方式 SuperType.call(this, name); //这里是函数调用方式,不是new方式 this.age = age; } //继承方法:原型链方式 SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){ alert(this.age); }
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black"); alert(instance1.colors);//"red,blue,green,black" instance1.sayName(); //"Nicholas" instance1.sayAge();//29 var instance2 = new SubType("Greg", 27); alert(instance2.colors);//"red,blue,green" instance2.sayName(); //"Greg" instance2.sayAge();//27
- SuperType 构造函数定义了两个属性:name 和 colors。SuperType 的原型定义了一个方法 sayName()
- SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了
- 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式
- instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象
- 组合继承最大的问题就是调用两次超类型构造函数
- 一次是在创建子类型原型的时候,二次是在子类型构造函数内部
- 这会导致子类型最终会包含超类型对象的全部实例属性,且在调用子类型构造函数时必须重写这些属性
SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name);//第二次调用超类 SuperType() this.age = age; } SubType.prototype = new SuperType(); //第一次调用超类 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }
- 上例中:
- 第一次调用SuperType 构造函数时,SubType.prototype 会得到两个继承自SuperType实例的属性:name 和colors,当前位于SubType 的原型中
- 当调用SubType 构造函数时,又会调用一次SuperType 构造函数,SubType 新对象获得了两个继承自SuperType实例的属性:name 和colors
- 问题出现:后面在SubType 新对象的name 和colors属性,会屏蔽了SubType 原型中的两个同名属性
3.1.3 寄生组合式继承
- 寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
- 基本思路是:
- 不必为了指定子类型的原型而调用超类型的构造函数,仅需超类型原型的一个副本(浅复制即能满足)
- 本质就是使用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型
//类似浅复制,也类似Object.create(object)
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype);//创建对象 prototype.constructor = subType;//增强对象 subType.prototype = prototype;//指定对象 }
- 上例inheritPrototype()函数实现了寄生组合式继承的最简单形式
- 这个函数接收两个参数:子类型构造函数和超类型构造函数
- 在函数内部
- 第一步是创建超类型原型的一个副本
- 第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性
- 第三步是将新创建的对象(即副本)赋值给子类型的原型。这样就可以用调用 inherit- Prototype()函数去替换下例中为子类型原型赋值的语句
- 上例只调用了一次 SuperType 构造函数,并且因此避免了在 SubType. prototype 上面创建不必要的、多余的属性
- 原型链还能保持不变;因此能够正常使用instanceof 和 isPrototypeOf()
- 寄生组合式继承是引用类型最理想的继承范
- 以下是寄生组合继承的终极写法:
function interitProtype(subType,superType){ var F =function(){}; F.prototype = superType.prototype; subType.prototype = new F(); subType.prototype.constructor = subType; subType.prototype.parent = superType; }

浙公网安备 33010602011771号