深入理解JS原型链与继承


觉得阅读精彩的文章是提升自己最快的方法,而且我发现人在不同阶段看待同样的东西都会有不同的收获,有一天你看到一本好书或者好的文章,请记得收藏起来,
隔断时间再去看看,我想应该会有很大的收获。其实今天要讨论的主题,有许多人写过许多精彩的文章,但是今天我还是想把自己的理解的知识记录下来。我在学习
掌握JS原型链和继承的时候,就是看得@阮一峰老师的写的文章,觉得他写的技术类的文章都容易让理解,简明概要,又好理解。他是我学习JS路程里面一个比较佩服的导师,昨天重新看了他写的《Javascript
面向对象编程
》第二部分和第三部份,这次的阅读的收获比以往都要多。同时也发现阮老师实现继承的方法还是有些缺陷,希望我修正后的继承能让更多人对JS有更深的理解。接下来我们从最基本的东西讲到最难的,希望能帮助大家更好的理解。

原型写法和用法

  1. function Cat(){

  2. this.Color = "black";

  3. this.eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var cat1 = new Cat();

  14. cat1.eat(); //吃老鼠

  15. cat1.A(); //Cat A

DEMO

上面这种写法是我们熟知的工厂模式,这种模式我觉得应该算是标准的原型写法。但是一般我不会使用这种写法,我会用直接量来写实现上面的方法,因为使用函数封装方法我个人感觉是很危险的,因为函数写法相当于是一个全局方法,他的执行顺序也是优先级最高的,这种方法不利于在大的项目中管理,所以一般用直接量,直接量最大的好处是只有当代码执行到这段代码后才会开始运行。接下来我们修改下上面的代码:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var cat1 = new Cat();

  14. cat1.eat(); //吃老鼠

  15. cat1.A(); //Cat A

DEMO

使用原型扩展提高性能


可能很多人会无法理解为什么我们要通过prototype来输入方法,也听过看过很多人说直接使用函数的效率是最低的,但是不知道原理。其实我们拿上面的案例做个简单的实验你或许就能懂了:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B =  function(){

  11. alert("Cat B");

  12. }

  13. var cat1 = new Cat();

  14. var cat2 = new Cat();

  15. alert(cat1.Eat == cat2.Eat) // false

  16. alert(cat1.A == cat2.A) // true

DEMO

看到结果是否很震惊,EatA方法都是Cat对象里面的,为什么两个实例一对比会产生不同的结果呢?在JS原型链中,通过prototype声明的方法会被存入内存中,不管我们实例化多少次Cat访问通过prototype扩展的A或者B方法,他们都是去读取同一个内存,但是Cat自身的属性和方法却不是这样,而是每次都会跟着实例化,如果该对象被频繁调用,那将会占用大量的内存,这就是为什么我们用prototype来扩展我们对象的属性和方法。

构造函数


上面这两种写法都是标准的原型写法,每个原型都有一个构造函数,每个原型的实例也都有一个构造函数。这个知识点非常的关键,这个构造函数你可以理解为和我们的身份证一样,每个原型构造函数都是唯一的,我们不能随意的去改变他们的身份证。我们来检测下上面的代码的构造函数。

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var cat1 = new Cat();

  14. console.log(Cat.prototype.constructor == Cat); //true

  15. console.log(cat1.constructor == Cat); //true

  16. console.log(cat1.constructor == Cat.prototype.constructor); //true

可能看到上面的有些人会说,你不是说每个构造函数都是一个身份证吗?为啥cat1的构造函数和Cat构造函数一样呢,坑爹吧。别急,这就是JS这门有趣的原因之一,cat1我们专业名词称它是Cat的实例,他们的构造函数是共享的。你也可以把cat1理解为Cat的一个复制品,或者说克隆人。我们可以无限复制Cat出来。

  1. var cat1 = new Cat();

  2. cat1.Eat(); //吃老鼠

  3. var cat2 = new Cat();

  4. cat2.Eat(); //吃老鼠

Cat的复制品的构造函数是都指向Cat本身的,记住这点。

原型继承


我觉得要正真理解原型链就需要先理解原型继承的原理,理解了如何继承,基本上你就对原型链掌握很深了。我们来实现一个简单的原型继承,我通过阮老师的文章中写的直接继承prototype来实现继承,修改上面的代码:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }

  16. Dog.prototype = Cat.prototype;

  17. console.log(Dog.prototype.constructor == Cat); // true

  18. var dog1 = new Dog();

  19. dog1.A(); // Cat A

  20. dog1.B(); // Cat B

上面的代码,我设置了一个Dog来继承Cat,我们使用prototype来实现继承,实际继承成功了,Dog的实例dog1调用了Cat里面的prototype的A和B方法。但是这里出了一个小问题,通过prototype继承导致了Dog的构造函数发生了改变,导致它指向了Cat,这就是我们代码中console输出的原因。我们上面说过每个原型都有一个自己的独立的构造函数,我们却改变了它,这样会导致原型混乱,所以我们必须把Dog的构造函数指回Dog本身。所以修改下代码:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }

  16. Dog.prototype = Cat.prototype;

  17. Dog.prototype.constructor = Dog;

  18. console.log(Dog.prototype.constructor == Cat); // false

  19. console.log(Cat.prototype.constructor == Dog); // true

  20. var dog1 = new Dog();

  21. dog1.A(); // Cat A

  22. dog1.B(); // Cat B

上面我通过Dog.prototype.constructor =
Dog;
这句话把Dog构造函数指回自己了,但是坑爹的是这样做之后,原先的Cat的构造函数也被改变成了Dog,唉,这是要闹哪样,完全坑爹,所以这种继承方式也是失败的,但是我们已经接近成功了,阮老师后面提出了利用空对象作为中介来继承。好的的直接上代码:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }
  16. var Fn = function(){};

  17. Fn.prototype = Cat.prototype;

  18. Dog.prototype = new Fn();

  19. Dog.prototype.constructor = Dog;

  20. console.log(Dog.prototype.constructor == Dog); // true

  21. console.log(Cat.prototype.constructor == Cat); // true

  22. var dog1 = new Dog();

  23. dog1.A(); // Cat A

  24. dog1.B(); // Cat B

DEMO

这下实现完美的继承了,上面是我根据阮老师的提供的方式实现的一个继承,Dog不止继承了Cat里面的prototype的方法,而且构造函数还是指回自己,Cat的构造函数也没被篡改。貌似非常完美的继承。但.....

prototype继承缺陷


上面的通过原型继承看起来很完美,但是还是有缺陷,并不是说@阮老师的方法有问题,他的继承方法是没问题的,但是只能针对空对象继承。

实际上通过prototype继承,他只能继承对象通过prototype的属性和方法,他无法继承对象本身的属性,举个例子:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }

  16. var Fn = function(){};

  17. Fn.prototype = Cat.prototype;

  18. Dog.prototype = new Fn();

  19. Dog.prototype.constructor = Dog;

  20. console.log(Dog.prototype.constructor == Dog); // true

  21. console.log(Cat.prototype.constructor == Cat); // true

  22. var dog1 = new Dog();

  23. dog1.A(); // Cat A

  24. dog1.B(); // Cat B

  25. console.log(dog1.Color);//undefined

  26. dog1.Eat();//has no method ‘Eat‘

我们在之前的代码里面调用了我们继承Cat的方法和属性,但是只有Cat里面的AB方法被调用成功了,但是Cat的自身属性里面的ColorEat方法都没调用成功,说明咱们根本没有继承到他自身的属性,只继承了通过prototype扩展的方法,这就是JS原型链的奇特现象之一,这种原型继承的缺陷貌似阮一峰老师也没发现。

所以我针对上面的方法做了些修改:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B = function(){

  11. alert("Cat B");

  12. };

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }

  16. Dog.prototype = new Cat();

  17. Dog.prototype.constructor = Dog;

  18. console.log(Dog.prototype.constructor == Dog); // true

  19. console.log(Cat.prototype.constructor == Cat); // true

  20. var dog1 = new Dog();

  21. alert(Dog.Weight);// 30

  22. dog1.A(); // Cat A

  23. dog1.B(); // Cat B

  24. alert(dog1.Color);//black

  25. dog1.Eat();//吃老鼠

DEMO

上面的我将继承者Dog的原型直接指向了Cat的实例,然后再将Dog的构造函数指回本身,这样就可以实现完整的继承了,Dog不仅仅继承了Catprototype而已还继承了Cat本身自带的属性和方法。在上面中我们知道Cat的实例cat1其实就是包含了Cat所有的属性和方法,他不会区分你是不是在原型中的方法还是在自身中的方法,都会完全被复制到实例中,所以我们直接去继承实例,这样子就可以直接获取到Cat中所有的方法和属性。而且直接继承实例我感觉也更加安全且高效,因为不去直接操作原型本身,只是操作原型实例。

通过深拷贝实现完美继承


其实上面的方法离完美的继承方式还是存在着一个缺陷的。我们的的继承者Dog如果他现在里面存在着原型方法的时候,我们又想让她保留现在的原型方法情况下,还可以去继承Cat里面的所有方法怎么办,用上面的方法是无法实现的,请看代码:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype ={

  8. A:function(){

  9. alert("Cat A");

  10. },

  11. B:function(){

  12. alert("Cat B");

  13. }

  14. }

  15. var Dog = function(){

  16. this.Weight = "30";

  17. }

  18. Dog.prototype.testDog = function(){

  19. alert("test Dog");

  20. }
  21. Dog.prototype = new Cat();

  22. Dog.prototype.constructor = Dog;
  23. var dog1 = new Dog();

  24. alert(dog1.Weight);//30

  25. dog1.testDog();//has no method ‘testDog‘

看上面的代码,我只是在继承者Dog的原型里面添加了一个testDog的方法,然后Dog用我们上面的方法去继承Cat后,Dog自身的属性Weight在继承Cat的过程中也被保留下来了,但是Dog存在原型链中的testDog却在继承过程中被干掉了,无言,心碎。这个时候我想到了阮一峰老师的拷贝继承,他的拷贝继承依然是存在的缺陷,但是我直接改进了他的方法,那样实现了完美的继承:

  1. var Cat = function(){

  2. this.Color = "black";

  3. this.Eat = function(){

  4. alert("吃老鼠");

  5. };

  6. }

  7. Cat.prototype.A = function(){

  8. alert("Cat A");

  9. };

  10. Cat.prototype.B =  function(){

  11. alert("Cat B");

  12. }

  13. var Dog = function(){

  14. this.Weight = "30";

  15. }

  16. Dog.prototype.testDog = function(){

  17. alert("test Dog");

  18. }
  19. var extend = function(Child,Parent){

  20. var p = new Parent();

  21. var c = Child.prototype;

  22. for (var i in p) {

  23. c[i] = p[i];

  24. }

  25. c.uber = p;

  26. }

  27. //用我们写好的继承方法执行继承

  28. extend(Dog,Cat);

  29. var dog1 = new Dog();

  30. dog1.A(); // Cat A

  31. dog1.Eat(); // 吃老鼠

  32. dog1.testDog(); // test Dog

  33. alert(dog1.Weight); // 30

DEMO

其实上面我们extend方法中我只是通过了for..in去遍历Cat生成的实例中的所有属性和方法,然后将这些值复制到我们的Dog中,这样子就可以实现保留本身属性又继承,这种方法是应该算是最优的解决方法。

转载来自520UEDhttp://www.520ued.com

时间: 2024-10-27 07:24:24

深入理解JS原型链与继承的相关文章

简单粗暴地理解js原型链--js面向对象编程

简单粗暴地理解js原型链--js面向对象编程 原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧,想点与代码无关的事,比如人.妖以及人妖. 1)人是人他妈生的,妖是妖他妈生的.人和妖都是对象实例,而人他妈和妖他妈就是原型.原型也是对象,叫原型对象. 2)人他妈和人他爸啪啪啪能生出一堆人宝宝.妖他妈和妖他爸啪啪啪能生出一堆妖宝宝,啪啪啪就是构造函数,俗

js原型链与继承(初体验)

js原型链与继承是js中的重点,所以我们通过以下三个例子来进行详细的讲解. 首先定义一个对象obj,该对象的原型为obj._proto_,我们可以用ES5中的getPrototypeOf这一方法来查询obj的原型,我们通过判断obj的原型是否与Object.prototype相等来证明是否存在obj的原型,答案返回true,所以存在.然后我们定义一个函数foo(),任何一个函数都有它的prototype对象,即函数的原型,我们可以在函数的原型上添加任意属性,之后通过new一个实例化的对象可以共享

浅谈js原型链与继承

js原型链与继承是js中的重点,所以我们通过以下三个例子来进行详细的讲解. 首先定义一个对象obj,该对象的原型为obj._proto_,我们可以用ES5中的getPrototypeOf这一方法来查询obj的原型,我们通过判断obj的原型是否与Object.prototype相等来证明是否存在obj的原型,答案返回true,所以存在.然后我们定义一个函数foo(),任何一个函数都有它的prototype对象,即函数的原型,我们可以在函数的原型上添加任意属性,之后通过new一个实例化的对象可以共享

对js原型链及继承的理解:__proto__&prototpye

首先我们来理解一下原型链的概念: 要理解原型链必须知道构造函数.原型和实例的关系: 每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(即prototype),而实例则包含一个指向原型对象的内部指针(即__proto__). var father = function() { this.relation = "father"; } father.prototype.money = function() { this.money = 100000; } var father

小谈js原型链和继承

原型(prototype)在js中可是担当着举足轻重的作用,原型的实现则是在原型链的基础上,理解原型链的原理后,对原型的使用会更加自如,也能体会到js语言的魅力. 本文章会涉及的内容 原型及原型对象 原型链(JavaScript核心部分) 类的继承 instanceof constructor 我们先用一个构造器来实现一个构造函数: function A(){ this.mark = "A"; this.changeMark = function(){ this.mark += &qu

简单粗暴地理解js原型链

原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧,想点与代码无关的事,比如人.妖以及人妖. 1)人是人他妈生的,妖是妖他妈生的.人和妖都是对象实例,而人他妈和妖他妈就是原型.原型也是对象,叫原型对象. 2)人他妈和人他爸啪啪啪能生出一堆人宝宝.妖他妈和妖他爸啪啪啪能生出一堆妖宝宝,啪啪啪就是构造函数,俗称造人. 3)人他妈会记录啪啪啪的信息,所以可

js 原型链和继承(转)

在理解继承之前,需要知道 js 的三个东西: 什么是 JS 原型链 this 的值到底是什么 JS 的 new 到底是干什么的 1. 什么是 JS 原型链? 我们知道 JS 有对象,比如 var obj = { name: "obj" }; 我们通过控制台把 obj 打印出来: 我们会发现 obj 已经有几个属性(方法)了.那么问题来了:valueOf / toString / constructor 是怎么来?我们并没有给 obj.valueOf 赋值呀. 上面这个图有点难懂,我手画

js原型链与继承

先看看JAVA中继承的定义 :Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类. 所以继承需要实现的是:能够拥有父类的方法和属性,也能自己定义新的方法和属性; 那么直接用原型链会有什么问题? //定义一个CarModel类 function CarModel(c){ this.color=c||"白色"; this.getColor=function(){ console.log('我的颜色是'+

理解JS 原型链 ( 一 )

原链接:http://blog.csdn.net/hongse_zxl/article/details/44622997 1 //定义父类Person,构造函数内有两个属性name和age,原型对象内定义了sayName方法 2 function Person(name, age) { 3 this.name = name; 4 this.age = age; 5 } 6 Person.prototype.sayName = function(){ 7 alert(this.name); 8 }