一,理解对象
1,属性类型
1)数据属性(为了表示数据是内部值放在[[]]之内)
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特征,能否把属性修改为访问器属性
[[Enumerable]]:表示能否通过for-in循环返回属性
[[Writable]]:表示能否修改属性的值
[[Value]]:包含这个属性的数据值,默认值为undefined
object.defineProperty(属性所在的对象,属性的名字,描述符对象) 修改属性默认的特性,
el:var person = {};
Object.defineProperty(person,name,{writable:false;value:zhang;enumerable:false;});
一旦属性定义为不可配置的,就不能再改变回来为可配置的了
2)访问器属性
访问器属性不包含数据值,包含一对getter和setter函数(不是必须的)读取时调用getter函数,写入时调用setter函数
[[Configurable]]:表示能否通过delete删除属性,能否修改属性值,能否把属性修改为数据属性
[[Enumerable]]:表示能否通过for-in循环返回属性
[[Get]]:读取属性时调用的函数,默认为undefined
[[Set]]:写入属性时调用的函数,默认为undefined
访问器属性不能直接调用,必修使用Object.defineProperty()来定义
_属性名:表示只能通过对象方法访问的属性,访问器属性最常见就是设置属性的值,改变其他属性的变化
创建访问器属性使用_defineGetter_()和_defineSetter_(),也可使用Object.defineProperty()
el:var book = {_year : 2004,edition : 1};
book._defineGetter_("year",function(){return this._year});
book._defineSetter_("year",function(newValue){if(new Value > 2004){this._year = newValue;this.edition += newValue - 2004;}});
2,定义多个属性
Object.defineProperties()可以一次性定义多个属性,
接收两个对象参数:要添加和修改其属性的对象,和描述符对象,要与第一个对象要添加和修改的属性相对应
3,读取属性的特性
Object.getOwnPropertyDescriptor(属性所在对象,读取其描述符的属性名称),获取给定属性的描述符,
返回值为一个对象,对于数据属性,返回对象的属性有configuraable,enumerable,writable,value
对于访问器属性,返回对象的属性有configurable,enumerable,get,set
二,创建对象
1,工厂模式
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(""); }; return 0; } var person1 = createPerson("asdg",29,"dasgj");
....
解决了创建多个相似对象的问题,没有解决对象识别的问题
2,构造函数模式
function createPerson(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(""); }; } var person1 = createPerson("asdg",29,"dasgj");
没有显示的创建对象,没有return,将属性和方法直接赋给了this对象
1)将构造函数当做函数
任何函数,只要通过new操作符调用都可以看做构造函数
2)构造函数的问题
每个方法都在每个实例上重新创建一遍,虽然可以将方法移到外面作为全局函数,但是全局函数不宜过多
构造函数中的方法可以定义在外面成为全局函数,当需要定义的方法很多的时候通过原型模式了
3,原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,包含可以由特定类型的所有实例共享和属性和方法
1)理解原型对象
function Person(){}
Person.prototype.name = "";
Person.prototype.age = 29;
...
只要创建了一个函数,就会为该函数创建一个prototype属性,指向函数的原型对象
所有的原型对象都会获得一个constructor(构造函数)属性,包含一个指向prototype属性所在函数的指针
实例的内部包含一个指针指向构造函数的原型对象 [[Prototype]]
所有实例都无法访问[[Prototype]] ,通过isPrototypeOf(), Person.prototype.isPrototypeOf(Person实例) 返回true
Object.getPrototypeOf(实例对象) 返回[[Prototype]]的值(实例所指向的构造函数的原型对象)
hasOwnProperty() 在给定属性存在于对象实例中,返回true,参数传进字符串形式属性名
Object.getOwnPropertyDescript() 在原型对象上调用返回原型属性
2)原型与in操作符
单独使用in操作符,对象能够访问的属性 都返回true,实例和原型都可
for-in 返回所有能够通过对象访问的,可枚举的,屏蔽了原型中不可枚举的([[Enumerable]]标记为false的)实例属性也可以返回
Object.keys() 方法接收一个对象作为参数,返回所有可枚举属性的字符串数组
Object.getOwnPropertyNames() 返回实例的所有属性,包括原型中的属性和不可枚举的属性
3)更简单的原型语法;
function Person(){} Person.prototype = { name : "", age : 310, ... };相当于重写原型函数
此时原型对象的constructor属性不指向Person,而是指向Object了
可以指定constructor的值为Person
Object.defineProperty(Person.prototype,"constructor",{enumerable:false,value:Person;});
4)原型的动态性
可以随时为原型添加属性和方法,,能够立即在实例中反映出来
重写原型函数,需要放在实例之前,否则无法访问到构造函数中的属性和方法
重写原型函数,切断了实例和原型之间的联系,实例[[prototype]]仍然指向最初的原型
5)原生对象的原型
可以给原生对象的原型添加属性和方法,也可以修改其属性和方法
6)原型对象的问题
通过在原型上添加一个同名的属性,可以屏蔽原型中的属性
4,组合使用构造函数模式和原型模式
构造函数模式用于定义实例,原型模式用于定义方法和共享属性,结果每个实例都有一份实例属性的副本,又共享方法的引用
改变实例的属性不会影响其他实例的属性
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["",""]; } Person.prototype = { constructor : Person, sayName : function(){}; }
定义实例可以分别使用自己的属性和共享方法
5,动态原型模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; if(typeof this.sayName != 'function'){ Persong.prototype.sayName = function(){}; } }
只有在sayName() 方法不存在的时候才能将其初始化,初次调用构造函数的时候才会使用,对原型所做的修改能立刻反映在实例中
6,寄生构造函数模式
function Person(name,age,job){ var o = new Object(); o.name = name ; o.age = age; o.job = job; o.sayName = function(){}; return o; }
函数中创建对象,并返回该函数
返回的对象与构造函数和构造函数的原型之间没有关系
7,稳妥构造函数模式
遵循与寄生构造函数类似的模型,区别在于创建对象的实例方法不引用this,不使用new操作符调用构造函数
三,继承
1,原型链
1)概念
原型链是实现基础的主要方法,利用原型让一个引用类型继承另一个引用类型的属性和方法
一个对象的原型 = 另个对象的实例 搜索时(实例-原型-继承原型)
2)别忘记默认的原型
所有函数的默认原型都是object的实例,默认原型都都包含一个内部指针,指向object.prototype
3)确定原型和实例的关系
instanceof,只要检测实例与原型链中出现的构造函数,结果就返回true (实例 instanceof 对象)
isPrototypeOf() ,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型 (实例.prototype.isPrototypeOf(实例))
4)谨慎的定义方法
继承了一个实例后,才能重写其中的方法
使用原型链继承不能使用字面量方法创建原型对象
5)原型链的问题
原型变成了 实例 则实例属性也就变成了原型属性了
创建子类的实例时,不能向超类型的构造函数传递参数
2,借用构造函数
解决原型中包含引用类型值所带来的问题
使用call() apply() 方法在将来新创建的对象上执行构造函数
1)传递参数
function SuperType(name){this.name = name; }
function SubType(){SuperType.call(this,"asdgasd");this.age = 20;}
传递了新的参数
2)借用构造函数的问题
无法避免构造函数模式存在的问题
3,组合继承
将原型链和借用构造函数结合
原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承
function SuperType(name){this.name = name;this.colors = ["","",""];}
SuperType.prototype.sayName = function(){alert("");}; 添加原型函数
function SubType(name,age){SuperType.call(this,name);this.age = age;} 继承name属性
SubType.prototype = new SuperType(); 原型链
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){};
instanceof isPrototypeof() 也用于判断组合继承的对象
缺点:调用两次超类型构造函数,一次是创建子类型原型的时候,二次是子类型构造函数的内部
4,原型式继承
function object(o){ function F(){} F.prototype = o; 传人的对象作为临时构造函数的原型 return new F(); 返回临时对象的实例 } var Person = {name:"",friend:["","",""]}; var anotherPerson = object(person); anotherPerson.name = ""; anotherPerson.friend.push(""); var yetanotherPerson = object(person); yetanotherPerson.name = ""; yetanotherPerson.friend.push(""); 1,Object.create(用作新对象原型的对象,为新对象定义额外属性的对象),规范化了原型继承 var person = { name:"Nicholas",friends:["shelby","",""] }; var anotherPerson = Object.create(person,{ name:{ value:"Greg" } });
5,寄生式继承
function createAnother(original){ var clone = object(original); clone.sayHi = function(){}; return clone; }
6,寄生组合式继承
1,思想:借用构造函数来继承属性,不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非只是超类型的副本而已
本质,使用寄生式继承来继承超类型的原型,将结果指定给子类型的原型
function inheritPrototype(subType,superType){ var prototype = object(superType,prototype); 创建对象 prototype.constructor = subType; 增强对象 subType.prototype = prototype; 指定对象 }