JavaScript原型
在JavaScript中,每个函数
都有一个prototype属性,当一个函数被用作构造函数来创建实例时,这个函数的prototype属性值会被作为原型赋值给所有对象实例。
所有函数对象的_proto_最终都指向Function.prototype,它是一个空函数.
代码:
function Son (name) { this.name = name; }
function Mother () { }
//Mother的原型
Mother.prototype = {
age: 18,
home: ['Beijing', 'Shanghai']
};
//Son的原型设为Mother
Son.prototype = new Mother();
var p1 = new Son('小明'); //p1:'小明'; __proto__:{__proto__:18,['Beijing','Shanghai']}
var p2 = new Son('蛋蛋'); //p2:'蛋蛋'; __proto__:{__proto__:18,['Beijing','Shanghai']}
//实例不会改变原型的基本值属性
p1.age = 20; //p1:'小明',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
//改变了原型Mother中的属性
// p1:'小明',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋'; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
p1.home[0] = 'Shenzhen';
//可以理解为p1.age=20
p1.home = ['Hangzhou', 'Guangzhou']; //p1:'小明',20,['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
//删除实例的属性之后,将会显示原本被覆盖的原型值,这就是向上的搜索机制
delete p1.age; //p1:'小明',['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
//为Son的原型Monther增加属性(改写原型,动态反应到实例中)
// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋'; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
Son.prototype.lastName = 'Li';
//更换Son的原型,就好像换了一个后妈一样
Son.prototype = {
age: 20,
address: { city: 'Ji nan' }
};
// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋'; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p3:'小花';__proto__: 20 {city: 'Ji nan'}
var p3 = new Son('小花');
//为Mother的原型增加属性,即为姥姥增加了属性
//但是上面因为更换了son的原型,所以Mother的变换不会影响p3
// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai'],233}
// p2:'蛋蛋'; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],233}
// p3: '小花';__proto__: 20 {city: 'Ji nan'}
Mother.prototype.adressnum = 233;
//更换Mother的原型,即更换了姥姥
//但是因为上面son的原型已经不是mother了,所以Mother怎么变不会影响Son。
Mother.prototype = {
car: 2,
hobby: ['run','walk']
};
var p4 = new Son('Tony'); //p4:'Tony';__proto__: 20 {city: 'Ji nan'}
//想让son应用这些改变的话,需要重新绑定mother
Son.prototype = new Mother(); //再次绑定
var p5 = new Son('小硕'); // p5:'小硕';__proto__:{__proto__: 2, ['run','walk']}
对于p1.home[0] = ‘Shenzhen’; 为何mother、p1和p2都受影响呢?
这是因为:p1中并不存在home这个数组。当你使用p1.home[0]时,在本地找不到home这个变量,这时由于原型链的向上搜索机制,会到p1的原型mother中寻找,找不到,接着到mother的原型中寻找,这里找到了home这个属性,于是就改变了mother原型的home[0]属性。
因此p1.home[0] = ‘Shenzhen’ 就等同于 Mother.prototype.home[0] = ‘Shenzhen’
原型链
原型链是实现继承的主要方法。
原型链的终点为null
原理:利用原型让一个引用类型继承另一个引用类型的属性和方法。
核心:属性共享和独立的控制
原型链继承的主要问题在于属性的共享:
组合继承
function Mother (age) {
this.age = age;
this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
console.log(this.age);
};
function Person (name, age) {
Mother.call(this, age);
this.name = name;
}
Person.prototype = new Mother(); //第一次执行mother
Person.prototype.constructor = Person;
Person.prototype.showName = function () {
console.log(this.name);
}
var p1 = new Person('Jack', 20); //第二次执行mother
p1.hobby.push('basketball'); //p1:'Jack'; __proto__:20,['running','football','basketball']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']
通过第二次执行原型的构造函数 Mother(),在对象实例中复制了一份原型的属性,这样就做到了与原型属性的分离独立。但是第一次调用 Mother(),好像什么用都没有,能不调用他吗?当然可以,于是就有了下面的寄生组合式继承。
寄生组合式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//避免了new Mother(),所以没了第一次执行
function inheritPrototype(Person, Mother){
var prototype = object(Mother.prototype);
prototype.constructor = Person;
Person.prototype = prototype;
}
function Mother (age) {
this.age = age;
this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
console.log(this.age);
};
function Person (name, age) {
Mother.call(this, age);
this.name = name;
}
inheritPrototype(Person, Mother);
Person.prototype.showName = function () {
console.log(this.name);
}
var p1 = new Person('Jack', 20);
p1.hobby.push('basketball');//p1:'Jack'; __proto__:20,['running','football','basketball']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']
关键点在于 object(o) 里面,这里借用了一个临时对象来巧妙避免了调用new Mother(),然后将原型为 o 的新对象实例返回,从而完成了原型链的设置。
Js创建对象的方法
//最原始模式,对象字面量方式
var person = {
name: 'Sun Miao',
age: 22,
sayName: function () { alert(this.name); }
};
最原始的模式不适合批量创建对象,批量创建可以考虑工厂模式
//工厂模式,定义一个函数创建对象
function creatPerson (name, age) {
var person = new Object();
person.name = name;
person.age = age;
person.sayName = function () {
alert(this.name);
};
return person;
}
工厂模式缺点是每次创建对象都会产生一个临时对象,而且你无法缺点创建的对象具体是什么类型,因为new Object().
这是可以考虑构造函数模式。
//构造函数模式,定义一个构造函数
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}
var p1 = new Person('Sun Miao', 22);
通过构造函数创建的对象,其中的方法(这里把方法看成是和name一样的变量就好)也都是各自独立的,比如sayName方法,如果我们想让多个对象共用一个sayName怎么办?
那就得用到原型模式了。
//原型模式1,直接定义prototype属性方式
function Person () {}
Person.prototype.name = 'Sun Miao';
Person.prototype.age = 22;
Person.prototype.sayName = function () { alert(this.name); };
//原型模式2,字面量定义方式
function Person () {}
Person.prototype = {
name: 'Sun Miao',
age: 22,
sayName: function () { alert(this.name); }
};
var p1 = new Person();
需要注意的是原型属性和方法的共享,即所有实例中都只是引用原型中的属性方法,任何一个地方产生的改动会引起其他实例的变化。
但我们只想共享某些方法,其他变量仍保持独立,那怎么办?
那就把原型和构造组合起来呗~
//原型+构造 模式
//对于要求独立的变量,使用构造模式
function Person (name, age) {
this.name = name;
this.age = age;
};
//对于要求共享的变量,使用原型模式
Person.prototype = {
hobby: ['warking','football'];
sayName: function () { alert(this.name); },
sayAge: function () { alert(this.age); }
};
var p1 = new Person('Sun Miao', 22);
//p1:'Sun Miao',22; __proto__: ['warking','football'],sayName,sayAge
var p2 = new Person('Wen Shuo', 23);
//p1:'Wen Shuo',23;__proto__: ['warking','football'],sayName,sayAge
这样做既节省了内存开销又保留了对象实例的独立性,一举两得。
参考文章
非常感谢茄果写的这篇关于原型理解的文章,看了之后很多模糊的地方都通了,例子比喻的很形象,通俗易懂。不像某些文章扯一堆官话,让人看一眼就没看下去的欲望了O(∩_∩)O哈哈~
http://www.cnblogs.com/qieguo/p/5451626.html
还有它写的关于闭包的文章也很出色,绝对是最好理解的文章。没有之一:
http://www.cnblogs.com/qieguo/p/5457040.html