网上关于介绍原型链的资料很是多,我也看了很多,怎么才能吃透一个知识点,一句话,本身上手,由于你光盯着代码看,你是看不出什么来的。。。chrome
我来讲说我本身的理解:数组
1】 写点儿与代码无关的,举个例子:你是你妈生的,光头强是光头强他妈生的,那么你和光头强就是对象的实例,你妈和光头强他妈就是原型,原型也是对象,叫原型对象浏览器
2】你妈和你爸造出你的过程,以及光头强的妈和他爸造出光头强的过程,就是构造函数,也就是造人,(有点儿小邪恶。。。可是通俗易懂不是?)函数
3】能够经过你和光头强找到大家的父母,也就是说能够经过原型找到构造函数工具
4】你妈和光头强他妈能生不少宝宝,可是你和光头强只有一个妈,这就是原型的惟一性测试
5】经过你能够找到你妈,经过你妈能够找到你外婆,经过你外婆能够找到你的外曾祖母,这个关系叫作原型链this
6】原型链并非无限的,一直网上找,发现你是猿猴的后代,是否是很刺激?原来你他妈他妈他妈。。。。都是不人,也就是原型链最终指向null (一切皆为空啊)prototype
7】你有你妈的大眼睛和小酒窝,光头强有他妈的秃顶,这就叫作继承调试
8】你继承了你妈的基因,你妈继承了你外婆的基因,。。。这就是原型链的继承code
9】你虽然继承了你妈的样式,可是你也能够来个皮裤汪的洗剪吹,也就是说对象的属能够自定义,名字相同的属性会覆盖继承获得的属性
10】虽然你洗剪吹变成黄毛了,可是你不能改变你妈的样子,你妈生的你弟弟妹妹跟你的黄毛没一点关系,这就是说对象实例不能改原型的属性
11】夏天你妈买了一个大西瓜,你跟你的弟弟妹妹都能 空调 wifi 西瓜,这就是原型的共享
12】你原本是外号放牛娃,可是你妈买彩票中了500万,别人都叫你:暴发户的儿子,这叫原型的动态性
13】紧接着你妈拿着这些钱去韩国整形,整得你外婆都不认识了,这叫原型的重写。
Show me my code
function Person (name) { this.name = name; }
function Mother () { }
Mother.prototype = { //Mother的原型
age: 18,
home: ['Beijing', 'Shanghai']
};
Person.prototype = new Mother(); //Person的原型为Mother
//用chrome调试工具查看,提供了__proto__接口查看原型,这里有两层原型,各位仍是直接看chrome好一点。
var p1 = new Person('Jack'); //p1:'Jack'; __proto__:{__proto__:18,['Beijing','Shanghai']}
var p2 = new Person('Mark'); //p2:'Mark'; __proto__:{__proto__:18,['Beijing','Shanghai']}
p1.age = 20;
/* 实例不能改变原型的基本值属性,正如你洗剪吹染黄毛跟你妈无关
* 在p1实例下增长一个age属性的普通操做,与原型无关。跟var o={}; o.age=20同样。
* p1:下面多了个属性age,而__proto__跟 Mother.prototype同样,age=18。
* p2:只有属性name,__proto__跟 Mother.prototype同样
*/
p1.home[0] = 'Shenzhen';
/* 原型中引用类型属性的共享,正如你烧了你家,就是烧了你全家的家
* 这个先过,下文再仔细唠叨一下可好?
* p1:'Jack',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark'; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
*/
p1.home = ['Hangzhou', 'Guangzhou'];
/* 其实跟p1.age=20同样的操做。换成这个理解: var o={}; o.home=['big','house']
* p1:'Jack',20,['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark'; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
*/
delete p1.age;
/* 删除实例的属性以后,本来被覆盖的原型值就重见天日了。正如你剃了光头,遗传的迷人小卷发就长出来了。
* 这就是向上搜索机制,先搜你,而后你妈,再你妈他妈,因此你妈的改动会动态影响你。
* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark'; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
*/
Person.prototype.lastName = 'Jin';
/* 改写原型,动态反应到实例中。正如你妈变新潮了,邻居提起你都说你妈真潮。
* 注意,这里咱们改写的是Person的原型,就是往Mother里加一个lastName属性,等同于Mother.lastName='Jin'
* 这里并非改Mother.prototype,改动不一样的层次,效果每每会有很大的差别。
* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark'; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}
*/
Person.prototype = {
age: 28,
address: { country: 'USA', city: 'Washington' }
};
var p3 = new Person('Obama');
/* 重写原型!这个时候Person的原型已经彻底变成一个新的对象了,也就是说Person换了个妈,叫后妈。
* 换成这样理解:var a=10; b=a; a=20; c=a。因此b不变,变得是c,因此p3跟着后妈变化,与亲妈无关。
* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark'; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}
* p3:'Obama';__proto__: 28 {country: 'USA', city: 'Washington'}
*/
Mother.prototype.no = 9527;
/* 改写原型的原型,动态反应到实例中。正如你妈他妈变新潮了,邻居提起你都说你丫外婆真潮。
* 注意,这里咱们改写的是Mother.prototype,p1p2会变,但上面p3跟亲妈已经了无瓜葛了,不影响他。
* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],9527}
* p2:'Mark'; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],9527}
* p3:'Obama';__proto__: 28 {country: 'USA', city: 'Washington'}
*/
Mother.prototype = {
car: 2,
hobby: ['run','walk']
};
var p4 = new Person('Tony');
/* 重写原型的原型!这个时候Mother的原型已经彻底变成一个新的对象了!人他妈换了个后妈!
* 因为上面Person与Mother已经断开联系了,这时候Mother怎么变已经不影响Person了。
* p4:'Tony';__proto__: 28 {country: 'USA', city: 'Washington'}
*/
Person.prototype = new Mother(); //再次绑定
var p5 = new Person('Luffy');
// 这个时候若是须要应用这些改动的话,那就要从新将Person的原型绑到mother上了
// p5:'Luffy';__proto__:{__proto__: 2, ['run','walk']}
p1.__proto__.__proto__.__proto__.__proto__ //null,你说原型链的终点不是null?
Mother.__proto__.__proto__.__proto__ //null,你说原型链的终点不是null?
看完基本能理解了吧?
如今再来讲说 p1.age = 20、p1.home = [‘Hangzhou’, ‘Guangzhou’] 和 p1.home[0] = ‘Shenzhen’ 的区别。 p1.home[0] = ‘Shenzhen’; 总结一下是 p1.object.method,p1.object.property 这样的形式。
p1.age = 20; p1.home = [‘Hangzhou’, ‘Guangzhou’];这两句仍是比较好理解的,先忘掉原型吧,想一想咱们是怎么为一个普通对象增长属性的:
var obj = new Object();
obj.name='xxx';
obj.num = [100, 200];
这样是否是就理解了呢?同样同样的呀。
那为何 p1.home[0] = ‘Shenzhen’ 不会在 p1 下建立一个 home 数组属性,而后将其首位设为 ‘Shenzhen’呢? 咱们仍是先忘了这个,想一想上面的obj对象,若是写成这样: var obj.name = ‘xxx’, obj.num = [100, 200],能获得你要的结果吗? 显然,除了报错你什么都得不到。由于obj还未定义,又怎么能往里面加入东西呢?同理,p1.home[0]中的 home 在 p1 下并未被定义,因此也不能直接一步定义 home[0] 了。若是要在p1下建立一个 home 数组,固然是这么写了:
p1.home = [];
p1.home[0] = 'Shenzhen';
这不就是咱们最经常使用的办法吗?
而之因此 p1.home[0] = ‘Shenzhen’ 不直接报错,是由于在原型链中有一个搜索机制。当咱们输入 p1.object 的时候,原型链的搜索机制是先在实例中搜索相应的值,找不到就在原型中找,还找不到就再往上一级原型中搜索……一直到了原型链的终点,就是到null还没找到的话,就返回一个 undefined。当咱们输入 p1.home[0] 的时候,也是一样的搜索机制,先搜索 p1 看有没有名为 home 的属性和方法,而后逐级向上查找。最后咱们在Mother的原型里面找到了,因此修改他就至关于修改了 Mother 的原型啊。
一句话归纳:p1.home[0] = ‘Shenzhen’ 等同于 Mother.prototype.home[0] = ‘Shenzhen’。
由上面的分析能够知道,原型链继承的主要问题在于属性的共享,不少时候咱们只想共享方法而并不想要共享属性,理想中每一个实例应该有独立的属性。所以,原型继承就有了下面的两种改良方式:
1)组合继承
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(); //第一次执行
Person.prototype.constructor = Person;
Person.prototype.showName = function () {
console.log(this.name);
}
var p1 = new Person('Jack', 20);
p1.hobby.push('basketball'); //p1:'Jack'; __proto__:20,['running','football']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']
这里第一次执行的时候,获得 Person.prototype.age = undefined, Person.prototype.hobby = [‘running’,’football’],第二次执行也就是 var p1 = new Person(‘Jack’, 20) 的时候,获得 p1.age =20, p1.hobby = [‘running’,’football’],push后就变成了 p1.hobby = [‘running’,’football’, ‘basketball’]。其实分辨好 this 的变化,理解起来也是比较简单的,把 this 简单替换一下就能获得这个结果了。 若是感受理解起来比较绕的话,试着把脑子里面的概念扔掉吧,把本身当浏览器从上到下执行一遍代码,结果是否是就出来了呢?
经过第二次执行原型的构造函数 Mother(),咱们在对象实例中复制了一份原型的属性,这样就作到了与原型属性的分离独立。细心的你会发现,咱们第一次调用 Mother(),好像什么用都没有呢,能不调用他吗?能够,就有了下面的寄生组合式继承。
2)寄生组合式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
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']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']
原型中再也不有 age 和 hobby 属性了,只有两个方法,正是咱们想要的结果!
关键点在于 object(o) 里面,这里借用了一个临时对象来巧妙避免了调用new Mother(),而后将原型为 o 的新对象实例返回,从而完成了原型链的设置。很绕,对吧,那是由于咱们不能直接设置 Person.prototype = Mother.prototype 啊。
小结
说了这么多,其实核心只有一个:属性共享和独立的控制,当你的对象实例须要独立的属性,全部作法的本质都是在对象实例里面建立属性。若不考虑太多,你大能够在Person里面直接定义你所须要独立的属性来覆盖掉原型的属性。总之,使用原型继承的时候,要对于原型中的属性要特别注意,由于他们都是牵一发而动全身的存在。
下面简单罗列下js中建立对象的各类方法,如今最经常使用的方法是组合模式,熟悉的同窗能够跳过到文章末尾点赞了。
1)原始模式
//1.原始模式,对象字面量方式
var person = {
name: 'Jack',
age: 18,
sayName: function () { alert(this.name); }
};
//1.原始模式,Object构造函数方式
var person = new Object();
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
alert(this.name);
};
显然,当咱们要建立批量的person一、person2……时,每次都要敲不少代码,资深copypaster都吃不消!而后就有了批量生产的工厂模式。
2)工厂模式
//2.工厂模式,定义一个函数建立对象
function creatPerson (name, age) {
var person = new Object();
person.name = name;
person.age = age;
person.sayName = function () {
alert(this.name);
};
return person;
}
工厂模式就是批量化生产,简单调用就能够进入造人模式(啪啪啪……)。指定姓名年龄就能够造一堆小宝宝啦,解放双手。可是因为是工厂暗箱操做的,因此你不能识别这个对象究竟是什么类型、是人仍是狗傻傻分不清(instanceof 测试为 Object),另外每次造人时都要建立一个独立的temp对象,代码臃肿,雅蠛蝶啊。
3)构造函数
//3.构造函数模式,为对象定义一个构造函数
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}
var p1 = new Person('Jack', 18); //建立一个p1对象
Person('Jack', 18); //属性方法都给window对象,window.name='Jack',window.sayName()会输出Jack
构造函数与C++、JAVA中类的构造函数相似,易于理解,另外Person能够做为类型识别(instanceof 测试为 Person 、Object)。可是全部实例依然是独立的,不一样实例的方法实际上是不一样的函数。这里把函数两个字忘了吧,把sayName当作一个对象就好理解了,就是说张三的 sayName 和李四的 sayName是不一样的存在,但显然咱们指望的是共用一个 sayName 以节省内存。
4)原型模式
//4.原型模式,直接定义prototype属性
function Person () {}
Person.prototype.name = 'Jack';
Person.prototype.age = 18;
Person.prototype.sayName = function () { alert(this.name); };
//4.原型模式,字面量定义方式
function Person () {}
Person.prototype = {
name: 'Jack',
age: 18,
sayName: function () { alert(this.name); }
};
var p1 = new Person(); //name='Jack'
var p2 = new Person(); //name='Jack'
这里须要注意的是原型属性和方法的共享,即全部实例中都只是引用原型中的属性方法,任何一个地方产生的改动会引发其余实例的变化。
5)混合模式(构造+原型)
//5. 原型构造组合模式,
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
hobby: ['running','football'];
sayName: function () { alert(this.name); },
sayAge: function () { alert(this.age); }
};
var p1 = new Person('Jack', 20);
//p1:'Jack',20; __proto__: ['running','football'],sayName,sayAge
var p2 = new Person('Mark', 18);
//p1:'Mark',18;__proto__: ['running','football'],sayName,sayAge
作法是将须要独立的属性方法放入构造函数中,而能够共享的部分则放入原型中,这样作能够最大限度节省内存而又保留对象实例的独立性。