原型链理解起来有点混乱,网上资料一大堆。每次晚上睡不着,总喜欢在网上找一些关于原型链和闭包的文章,效果极佳。别纠结那一堆术语,除了把你的脑子拧成麻花外,真的帮不了你。简单粗略的看原型链,想想人、妖、异装癖等与代码无关的东西。1)人生于人,魔生于魔。人和魔是对象实例,而人和魔是原型。原型也是一个对象,称为原型对象。2)人母鬼父可以生一堆人娃,妖母妖爹可以生一堆妖娃。Papapa就是构造函数,俗称造人。3)人会记录性别信息,所以可以通过人找到性别信息,也就是说可以通过原型对象找到构造函数。4)一个妈妈可以生很多个宝宝,但是这些宝宝只有一个妈妈,这是原型的唯一性。5)人类也是人类他妈的妈妈生的。通过人类他妈可以找到人类他妈,通过人类他妈可以找到人类他妈……这种关系叫做原型链。6)原型链不是无限的。当你不断地翻看人时,你终会发现人不是人。也就是说,原型链最终指向null。7)人母生的人长得像人,鬼生的鬼长得像鬼。这称为继承。8)你遗传了你妈的肤色,你妈遗传了你妈他妈的肤色,你妈的妈……,这就是原型链的传承。9)如果你没有家,那么你家就是你妈妈的房子;如果你妈妈没有家,那你家就是你妈妈的房子……这就是原型链的向上查找。10)你会继承你妈妈的容貌,但你也可以染发、洗头、剪发、吹发,这意味着对象的属性可以自定义,并且会覆盖继承的属性。11)虽然你洗头发、剪头发、吹头发、染黄头发,都改变不了你妈妈的样子。你妈妈的弟弟妹妹跟你的黄头发没有关系,也就是说对象实例不能改变原型的属性。12)但如果你的房子被你玩火烧掉了,那就意味着你的房子,你妈妈的房子,你弟弟的房子都被烧毁了。这就是原型属性的共享。13)你妈妈的小名是阿珍,邻居家的阿姨都叫你阿珍儿,但是你妈妈的毛从飘柔变成了金毛狮王之后,隔壁的阿姨就把名字改成了你,叫你金毛狮子王子。这称为原型的动态特性。14)你妈爱美,去韩国整容,让你妈都认不出来了,就算你妈的头发换回飘柔,隔壁邻居还是叫你金狮王子。因为没人认得你妈,整容后的你妈重制了,也就是原型的整体改写。尼玛!你够了!别BB!给我看代码!functionPerson(name){this.name=name;}functionMother(){}Mother.prototype={//妈妈原型age:18,home:['北京','上海']};Person.prototype=newMother();//Person的原型是Mother//用chrome调试工具查看,提供__proto__接口查看原型varp1=newPerson('Jack');//p1:'Jack';__proto__:18,['北京','上海']varp2=newPerson('Mark');//p2:'Mark';__proto__:18,['北京','上海']p1.age=20;/*实例不能改变prototype属性的基本值,就像洗剪吹染你的黄头发跟你妈妈没有关系*p1实例下添加一个age属性的普通操作跟prototype没有关系。与varo={};o.age=20相同。*p1:下面多了一个属性age,__proto__和Mother.prototype一样,age=18。*p2:只是属性名,__proto__与Mother.prototype*/p1.home[0]='Shenzhen';/*prototype中引用类型属性的共享,就像你烧了你的家一样,你烧了你的全家Home*这个先过一遍吧,以后再说好吗?*p1:'杰克',20;__proto__:18,['深圳','上海']*p2:'马克';__proto__:18,['深圳','上海']*/p1.home=['杭州','广州'];/*其实和p1.age=20一样的操作。换成这样理解:varo={};o.home=['big','house']*p1:'Jack',20,['Hangzhou','Guangzhou'];__proto__:18,['Shenzhen','Shanghai']*p2:'Mark';__proto__:18,['Shenzhen','Shanghai']*/deletep1.age;/*删除实例的属性后,原来被覆盖的原型值会被恢复。就像您剃光头一样,您继承的迷人小卷发会重新长出来。*这是向上搜索机制,先搜索你,再搜索你妈妈,再搜索你妈妈的妈妈,所以你妈妈的变化会动态影响你。*p1:'杰克',['杭州','广州'];__proto__:18,['深圳','上海']*p2:'马克';__proto__:18,['深圳','上海']*/Person.prototype.lastName='Jin';/*重写原型,动态响应实例。就像你妈妈变潮了,邻居说你是潮女人的儿子*注意,这里我们重写Person的原型,就是给Mother增加一个lastName属性,相当于Mother.lastName='Jin'*这不是换母。原型和改变不同的级别往往会有非常不同的效果。*p1:'Jack',['杭州','广州'];__proto__:'jin';__proto__:18,['深圳','上海']*p2:'Mark';__proto__:'jin';__proto__:18,['深圳','上海']*/Person.prototype={age:28,address:{country:'USA',city:'Washington'}};varp3=newPerson('Obama');/*重写原型!这时候Person的原型已经完全变成了一个新的对象,也就是说Person换了妈妈,叫继母。*用这个理解替换它:vara=10;b=a;a=20;c=a。所以b不变,变成了c,所以p3跟继母变了,跟亲妈没关系。*p1:'Jack',['杭州','广州'];__proto__:'jin';__proto__:18,['深圳','上海']*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',['杭州','广州'];__proto__:'jin';__proto__:18,['深圳','上海'],9527*p2:'Mark';__proto__:'jin';__proto__:18,['深圳','上海'],9527*p3:'奥巴马';__proto__:28{国家:'美国',城市:'华盛顿'}*/Mother.prototype={car:2,hobby:['run','walk']};varp4=newPerson('Tony');/*重写原型的原型!这时候,母亲的原型已经完全变成了一个新的对象!他妈换了他后妈!*因为上面提到的Person和Mother已经断开连接,此时Mother如何变化不会影响到Person。*p4:'Tony';__proto__:28{country:'USA',city:'Washington'}*/Person.prototype=newMother();//重新绑定varp5=newPerson('Luffy');//这个如果你需要应用这些更改,你需要将Person的原型重新绑定到mother//p5:'Luffy';__proto__:2,['run','walk']p1.__proto__.__proto__.__proto__。__proto__//null,你说原型链的末尾不为null?娘.__proto__.__proto__.__proto__//null,你说原型链的末尾不为null?看完你能基本看懂了吗?下面说说p1.age=20,p1.home=['杭州','广州']和p1.home[0]='深圳'的区别。p1.home[0]='深圳';总结起来就是p1.object.method,p1.object.property的形式。p1.age=20;p1.home=['杭州','广州'];这两句话比较容易理解。忘掉原型,想想我们如何给一个普通的对象添加属性:varobj=newObject();obj.name='xxx';obj.num=[100,200];这可以理解吗?一样。那为什么p1.home[0]='Shenzhen'不在p1下创建一个home数组属性,然后把它的第一位设为'Shenzhen'呢?这个我们先算了,想想上面的obj对象,如果这样写:varobj.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)组合继承functionMother(age){this.age=age;this.hobby=['running','football']}Mother.prototype.showAge=function(){console.log(this.age);};functionPerson(name,age){Mother.call(this,age); //第二次执行this.name=name;}Person.prototype=newMother(); //Person.prototype.constructor=Person;Person.prototype.showName=function(){console.log(this.name);}varp1=newPerson('Jack',20);p1.hobby.push('basketball');//p1:'Jack';__proto__:20,['running','football']varp2=newPerson('Mark',18);//p2:'Mark';__proto__:18,['running','football']结果为红色:第一次执行时,得到Person.prototype.age=undefined,Person.prototype.hobby=['running','football'],第二次执行是当varp1=newPerson('Jack',20),getp1.age=20,p1.hobby=['running','football'],push后变成p1.hobby=['running','足球','篮球']。其实区分this的变化比较容易理解,简单的替换this就可以得到结果。如果你觉得理解起来很费解,就试着抛开脑子里的概念,像浏览器一样从上到下执行代码,结果会出来吗?通过第二次执行原型的构造函数Mother(),我们在对象实例中复制了一份原型的属性,从而实现了与原型属性的分离和独立。如果你细心,你会发现我们第一次调用Mother(),好像没用。我们不能叫他吗?是的,有下面的寄生成分继承。2)寄生组合继承functionobject(o){functionF(){}F.prototype=o;returnnewF();}functioninheritPrototype(Person,Mother){varprototype=object(Mother.prototype);prototype.constructor=Person;Person。prototype=prototype;}functionMother(age){this.age=age;this.hobby=['running','football']}Mother.prototype.showAge=function(){console.log(this.age);};functionPerson(name,age){Mother.call(this,age);this.name=name;}inheritPrototype(Person,Mother);Person.prototype.showName=function(){console.log(this.name);}varp1=newPerson('Jack',20);p1.hobby.push('basketball');//p1:'Jack';__proto__:20,['running','football']varp2=newPerson('Mark',18);//p2:'Mark';__proto__:18,['running','football']结果为红色:原型中不再有age和hobby属性,只有两个方法,正好是我们想要的结果!重点在object(o)中,借用了一个临时对象,巧妙地避免调用newMother(),然后返回原型为o的新对象实例,这样就完成了原型链的设置。绕口,对,那是因为我们不能直接设置Person.prototype=Mother.prototype。总结说了这么多,其实核心只有一个:属性共享和独立控制。当你的对象实例需要独立的属性时,所有方法的本质都是在对象实例中创建属性。如果不想太多,可以直接在Person中定义自己需要的独立属性来覆盖原型的属性。总之,在使用原型继承的时候,要特别注意原型中的属性,因为它们都是牵一发而动全身的。下面简单罗列一下js中创建对象的各种方法。现在最常用的方法是组合模式。熟悉的同学可以跳到文末点赞。1)原始模式//1。原始模式,对象字面量方法varperson={name:'Jack',age:18,sayName:function(){alert(this.name);}};//1.原始模式,对象构造方法varperson=newObject();person.name='Jack';person.age=18;person.sayName=function(){alert(this.name);};显然,当我们要批量创建person1,person2...时,每次都要敲一大堆代码,连资深的copypaster都受不了!然后是批量生产的工厂模式。2)工厂模式//2.工厂模式,定义创建对象的函数functioncreatPerson(name,age){vartemp=newObject();person.name=name;person.age=age;person.sayName=function(){alert(this.name);};returntemp;}工厂模式为量产,只需简单调用(啪啪啪……)即可进入人工创造模式。您可以通过指定姓名和年龄来创建一堆婴儿,解放您的双手。但是因为是在工厂里暗箱操作,你无法识别它是什么类型的对象,是人还是狗(测试的instanceof是Object),每次都需要创建一个独立的temp对象你创造了一个人。代码臃肿,飘逸如蝶。3)构造函数//3.构造函数模式,为对象定义一个构造函数functionPerson(name,age){this.name=name;this.age=age;this.sayName=function(){alert(this.name);};}varp1=newPerson('Jack',18);//创建一个p1对象Person('Jack',18);//属性和方法给window对象,window.name='Jack',window.sayName()会输出Jack的构造函数,类似于C++和JAVA中的类构造函数,很容易理解。另外,Person可以被识别为一个类型(instanceoftest是Person和Object)。但是所有的实例还是独立的,不同实例的方法其实是不同的函数。这里忘记function这个词,把sayName理解成一个对象很容易理解,也就是说张三的sayName和李四的sayName是不一样的,但是显然我们期望共享一个sayName来节省内存。4)原型模式//4.原型模式,直接定义原型属性functionPerson(){}Person.prototype.name='Jack';Person.prototype.age=18;Person.prototype.sayName=function(){alert(this.name);};//4。原型模式,字面量定义方法functionPerson(){}Person.prototype={name:'Jack',age:18,sayName:function(){alert(this.name);}};varp1=newPerson();//name='Jack'varp2=newPerson();//name='Jack'这里需要注意的是原型属性和方法的共享,即所有实例只在任何一个地方引用原型属性方法导致其他实例发生变化。5)混合模式(构造+原型)//5。原型构造组合方式,functionPerson(name,age){this.name=name;this.age=age;}Person.prototype={hobby:['running','football'];sayName:function(){alert(this.name);},sayAge:function(){alert(this.age);}};varp1=newPerson('Jack',20);//p1:'Jack',20;__proto__:['running','football'],sayName,sayAgevarp2=newPerson('Mark',18);//p1:'Mark',18;__proto__:['running','football'],sayName,sayAge的做法是把将需要独立的属性方法放到构造函数中,将可共享的部分放到原型中,可以最大程度节省内存,同时保持对象实例的独立性。
