目录
加一Q一带一你10319281邀一情一玛33339333进【c9183.com】已助上千人成功翻盘,欢迎增加,沟通交流!
1 原型链
1.1 原型
1.1.1 什么是原型?
1.1.2 原型的作用:数据共享,节俭内存空间
1.1.3 原型的写法:
1.1.4 通过原型为内置对象增加原型的属性或者办法
1.2 原型链
1.3 原型的指向
1.3.1 原型的指向是能够扭转的
1.3.2 原型的最终指向
1.3.3 在原型扭转指向之后增加原型办法
1.4 实例对象属性和原型对象属性重名
1.5 一条神奇的原型链
2 继承
2.1 什么是继承
2.2 通过原型实现继承
2.3 继承实例
2.4 借用构造函数实现继承
2.5 组合继承
2.6 另一种继承形式:拷贝继承(for-in)
3 总结
1 原型链
1.1 原型
对于原型在JS面向对象编程这篇文章曾经讲过了,明天简略来温习一下。
1.1.1 什么是原型?
在JS构造函数中有一个属性prototype,叫做原型,这是给程序员应用的。在JS实例对象中有一个属性__proto__,它也是原型,这是供浏览器应用的,它不是规范的属性。 实例对象中的__proto__指向的是该实例对象中的构造函数中的prototype,构造函数中的prototype外面的属性或者办法,能够间接通过实例对象调用。
个别状况下,实例对象.__proto__能力拜访到构造函数中的prototype的属性或者办法,即 per._proto_.eat()。 因为__proto__不是规范的属性,所以间接写成 per.eat()即可,原型是一个属性,而这个属性也是一个对象。
1.1.2 原型的作用:数据共享,节俭内存空间
在构造函数中定义的属性和办法,当实例化对象的时候,实例对象中的属性和办法都是在本人的空间中存在的,如果是多个对象。这些属性和办法都会在独自的空间中存在,节约内存空间,所以,为了数据共享,把想要节俭空间的属性或者办法写在原型对象中,达到了数据共享,节俭了内存空间。
1.1.3 原型的写法:
构造函数.prototype.属性=值
构造函数.prototype.办法=值---->函数.prototype,函数也是对象,所以,外面也有__proto__
实例对象.prototype-------->实例对象中没有这个属性,只有__proto__
原型的简略写法
缺点:--->原型间接指向{}---->就是一个对象,没有结构器
1. 构造函数.prototype={ 2. 切记:如果这这种写法,要把结构器加上 4. };
1.1.4 通过原型为内置对象增加原型的属性或者办法
零碎的内置对象的属性和办法可能不满足当初需要,所以能够通过原型的形式退出属性或者办法。为内置对象的原型增加属性和办法,这个内置对象的实例对象能够间接应用增加的属性或办法。
1. //为内置对象增加原型办法 2. //咱们在零碎的对象的原型中增加办法,相当于在扭转源码 3. //我心愿字符串中有一个倒序字符串的办法 4. String.prototype.myReverse = function() { 5. for (var i = this.length - 1; i >= 0; i--) { 6. console.log(this[i]); 7. } 8. }; 9. var str = "abcdefg"; 10. str.myReverse(); 13. //为Array内置对象的原型对象中增加办法 14. Array.prototype.mySort = function() { 15. for (var i = 0; i < this.length - 1; i++) { 16. for (var j = 0; j < this.length - 1 - i; j++) { 17. if (this[j] < this[j + 1]) { 18. var temp = this[j]; 19. this[j] = this[j + 1]; 20. this[j + 1] = temp; 21. } //end if 22. } // end for 23. } //end for 24. }; 26. var arr = [100, 3, 56, 78, 23, 10]; 27. arr.mySort(); 28. console.log(arr); 31. String.prototype.sayHi = function() { 32. console.log(this + "哈哈,我又变帅了"); 33. }; 35. //字符串就有了打招呼的办法 36. var str2 = "小杨"; 37. str2.sayHi();
1.2 原型链
原型链是一种关系,是实例对象和原型对象之间的关系,这种关系是通过原型(proto)来分割的。JavaScript 对象有一个指向一个原型对象的链。当试图拜访一个对象的属性时,它不仅仅在该对象上搜查,还会搜查该对象的原型,以及该对象的原型的原型,顺次层层向上搜寻,直到找到一个名字匹配的属性或达到原型链的开端。
请看下边的代码:
1. // 人的构造函数 2. function Person(name,age) { 3. // 属性 4. this.name=name; 5. this.age=age; 6. // 在构造函数中的办法 7. this.eat=function () { 8. console.log("吃吃吃"); 9. }; 10. } 11. // 增加共享的属性 12. Person.prototype.sex="男"; 13. // 增加共享的办法 14. Person.prototype.sayHi=function () { 15. console.log("哈哈哈"); 16. }; 17. // 实例化对象,并初始化 18. var per=new Person("张三",18); 19. per.sayHi();
上边的代码中实例对象的原型__proto__和构造函数的原型prototype指向是雷同的,实例对象中的__proto__原型指向的是构造函数中的原型prototype。
console.log(per.__proto__==Person.prototype);// true
它们的关系如下:
上图中红线链接局部即Person实例对象和Person原型对象之间的关系,能够看成一个原型链(不残缺,目前能够先这样了解)。
1.3 原型的指向
1.3.1 原型的指向是能够扭转的
请看上面的代码:
1. //人的构造函数 2. function Person(age) { 3. this.age=10; 4. } 5. //人的原型对象办法 6. Person.prototype.eat=function () { 7. console.log("吃吃吃!!!"); 8. }; 9. //学生的构造函数 10. function Student() { 12. } 13. Student.prototype.sayHi=function () { 14. console.log("学学学,为中华崛起而读书!!!"); 15. }; 16. //学生的原型,指向了一个人的实例对象 17. Student.prototype=new Person(18); 18. var stu=new Student(); 19. console.dir(stu); 20. stu.eat(); // 吃吃吃!!! 21. stu.sayHi();//谬误stu.sayHi is not a function,原型指向产生扭转,找不到sayHi()办法
输入后果:
上边的代码中,实例对象stu调用原型办法sayHi()的时候产生谬误,这是因为Student.prototype=new Person(18);使原型指向产生了扭转,stu对象中曾经找不到sayHi()办法。原型指向扭转剖析: 图中的红线形成原型链。
1.3.2 原型的最终指向
通过上边的学习咱们能够晓得,实例对象中有__proto__原型,构造函数中有prototype原型,prototype也是是一个对象,那么,prototype这个对象中应该也有__proto__,那么它又指向了哪里呢?
即然实例对象中的__proto__指向的是构造函数的prototype,所以prototype这个对象中__proto__指向的应该是某个构造函数的原型prototype。
1. function Person() { 3. } 4. Person.prototype.eat=function () { 5. console.log("吃吃吃"); 6. }; 8. var per=new Person(); 9. console.dir(per); 10. console.dir(Person);
通过剖析咱们发现Person的prototype中的__proto__的指向为Object;Object.prototype的__proto__是null。
console.log(Object.prototype.__proto__);// null
所以per实例对象的原型的指向为:
per实例对象的__proto__------->Person.prototype,Person.prototype的__proto__---->Object.prototype,Object.prototype的__proto__是null。这形成了一个残缺的原型链。
上图中的红线链接形成了一个残缺的原型链。
1.3.3 在原型扭转指向之后增加原型办法
在1.3.1的代码中,因为原型指向产生扭转,找不到sayHi()办法而产生谬误,咱们能够在原型扭转指向之后增加原型办法,扭转代码如下:
1. //人的构造函数 2. function Person(age) { 3. this.age=10; 4. } 5. //人的原型对象办法 6. Person.prototype.eat=function () { 7. console.log("吃吃吃!!!"); 8. }; 9. //学生的构造函数 10. function Student() { 12. } 13. //学生的原型,指向了一个人的实例对象 14. Student.prototype=new Person(18); 15. Student.prototype.sayHi=function () { 16. console.log("学学学,为中华崛起而读书!!!"); 17. }; 18. var stu=new Student(); 19. console.dir(stu); 20. stu.eat(); // 吃吃吃!!! 21. stu.sayHi();// 学学学,为中华崛起而读书!!!
留神:简略的原型写法,会将 Person.prototype 重置到了一个新的对象,即扭转原型的指向。咱们也应该在原型扭转指向之后增加原型办法。
1. function Person(age) { 2. this.age = age; 3. } 5. //指向扭转了 6. Person.prototype = { 7. eat: function () { 8. console.log("吃"); 9. } 10. }; 11. //增加原型办法 12. Person.prototype.sayHi = function () { 13. console.log("你好帅呀!!!"); 14. }; 15. var per = new Person(10); 16. per.sayHi();// 你好帅呀!!!
1.4 实例对象属性和原型对象属性重名
实例对象拜访某个属性,应该先从实例对象中找,找到了就间接用,找不到就去指向的原型对象中找,找到了就应用,找不到返回undefined。
1. function Person(age,sex) { 2. this.age=age; 3. this.sex=sex; 4. } 5. Person.prototype.sex="女"; 6. var per=new Person(18,"男"); 7. console.log(per.sex); // 男 8. // 因为JS是一门动静类型的语言,如果对象没有这个属性,只有对象.属性名字,对象就有了这个属性了 9. // 然而该属性没有赋值, 所以per.albert后果是:undefined 10. console.log(per.albert); // undefined 11. console.log(albert); // 报错
通过实例对象不能扭转原型对象中的属性值,要想扭转原型对象中的属性值,应该间接通过原型对象.属性=值;进行扭转。
1. Person.prototype.sex="我的天呐!!!我为什么这么帅?"; 2. per.sex="不晓得"; 3. console.log(per.sex); 5. console.dir(per);
1.5 一条神奇的原型链
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="UTF-8"> 5. <title>微信公众号:AlbertYang</title> 6. </head> 7. <body> 8. <div id="dv"></div> 9. <script> 10. var divObj = document.getElementById("dv"); 11. console.dir(divObj); 12. </script> 13. </body> 14. </html>
通过在控制台剖析divObj,得出divObj原型链指向为:
divObj.proto---->HTMLDivElement.prototype的__proto__--->HTMLElement.prototype的__proto__---->Element.prototype的__proto__---->Node.prototype的__proto__---->EventTarget.prototype的__proto__---->Object.prototype没有__proto__,所以,Object.prototype中的__proto__是null
2 继承
面向对象编程思维是依据需要剖析对象,找到对象有什么特色和行为,而后通过代码的形式来实现需求。要想实现这个需要,就要创建对象,要想创建对象,就应该有构造函数,而后通过构造函数来创建对象,通过对象调用属性和办法来实现相应的性能及需要。
因为面向对象的思维适宜于人的想法,编程起来会更加的不便,前期保护的时候也要会更加容易,所以咱们才要学习面向对象编程。但JS不是一门面向对象的语言,而是一门基于对象的语言。JS不像JAVA,C#等面向对象的编程语言中有类(class)的概念(也是一种非凡的数据类型),JS中没有类(class),然而JS能够模仿面向对象的思维编程,在JS能够通过构造函数来模仿类的概念(class)。在ES6中,class (类)作为对象的模板被引入,能够通过 class 关键字定义类,然而它的的实质还是 function。
2.1 什么是继承
继承是一品种(class)与类之间的关系,JS中没有类,然而能够通过构造函数模仿类,而后通过原型来实现继承,继承是为了实现数据共享,js中的继承当然也是为了实现数据共享。
继承是子类继承父类的特色和行为,使得子类对象(实例)具备父类的属性和办法,或子类从父类继承办法,使得子类具备父类雷同的行为。继承能够使得子类具备父类的各种属性和办法,而不须要再次编写雷同的代码。
例如:人有 姓名, 性别, 年龄 ,吃饭, 睡觉等属性和行为。
学生有: 姓名, 性别, 年龄 ,吃饭, 睡觉 学习等属性和行为。
老师有: 姓名, 性别, 年龄 ,吃饭, 睡觉 ,教学等属性和行为。
先定义一个人类,人有姓名, 性别, 年龄等属性,有,吃饭, 睡觉等行为。由人这个类派生出学生和老师两个类,为学生增加学习行为,为老师增加教学行为。
2.2 通过原型实现继承
1. // 人 2. function Person(name, age, sex) { 3. this.name = name; 4. this.sex = sex; 5. this.age = age; 6. } 7. Person.prototype.eat = function() { 8. console.log("吃吃吃!!!"); 9. }; 10. Person.prototype.sleep = function() { 11. console.log("睡睡睡!!!"); 12. }; 15. //学生 16. function Student(score) { 17. this.score = score; 18. } 19. //扭转学生的原型的指向即可让学生继承人 20. Student.prototype = new Person("张三", 18, "男"); 21. Student.prototype.study = function() { 22. console.log("学习真的太累了!!!"); 23. }; 25. var stu = new Student(99); 26. console.log("学生从人中继承的属性和行为:"); // >学生从人中继承的属性和行为: 27. console.log(stu.name); // >张三 28. console.log(stu.age); // >18 29. console.log(stu.sex); // >男 30. stu.eat(); // >吃吃吃!!! 31. stu.sleep(); // >睡睡睡!!! 32. console.log("学生中本人有的属性和行为:"); // >学生中本人有的属性和行为: 33. console.log(stu.score); // >99 34. stu.study(); // >学习真的太累了!!!
2.3 继承实例
动物有名字,体重等属性,还有吃货色的行为。
狗有名字,体重,毛色等属性, 还有吃货色和咬人的行为。
哈士奇有名字,体重,毛色,年龄等属性, 还有吃货色,咬人, 逗人玩等行为。
它们之间的继承关系代码实现如下:
1. // 动物的构造函数 2. function Animal(name, weight) { 3. this.name = name; 4. this.weight = weight; 5. } 6. //动物的原型的办法 7. Animal.prototype.eat = function() { 8. console.log("弟兄们冲啊,赶快吃吃吃!!!"); 9. }; 11. //狗的构造函数 12. function Dog(color) { 13. this.color = color; 14. } 15. Dog.prototype = new Animal("小三", "30kg"); 16. Dog.prototype.bitePerson = function() { 17. console.log("~汪汪汪~,快让开,我要咬人了!!!"); 18. }; 20. //哈士奇构造函数 21. function Husky(age) { 22. this.age = age; 23. } 24. Husky.prototype = new Dog("黑红色"); 25. Husky.prototype.playYou = function() { 26. console.log("咬坏充电器,咬坏耳机,拆家...哈哈,好玩不!!!"); 27. }; 28. var husky = new Husky(3); 29. console.log(husky.name, husky.weight, husky.color); 30. husky.eat(); 31. husky.bitePerson(); 32. husky.playYou();
它们的原型链关系为:
2.4 借用构造函数实现继承
在上边的解说中,咱们为了数据共享,扭转了原型指向,做到了继承,即通过扭转原型指向实现了继承。这导致了一个问题,因为咱们扭转原型指向的同时,间接初始化了属性,这样继承过去的属性的值都是一样的了。这是个问题,如果咱们想要扭转继承过去的值,只能从新调用对象的属性进行从新赋值,这又导致咱们上边的初始化失去了意义。
1. function Person(name, age, sex, weight) { 2. this.name = name; 3. this.age = age; 4. this.sex = sex; 5. this.weight = weight; 6. } 7. Person.prototype.sayHi = function() { 8. console.log("你好帅呀!!!"); 9. }; 11. function Student(score) { 12. this.score = score; 13. } 14. //心愿人的类别中的数据能够共享给学生---继承 15. Student.prototype = new Person("小三", 18, "男", "58kg"); 17. var stu1 = new Student("99"); 18. console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score); 19. stu1.sayHi(); 21. var stu2 = new Student("89"); 22. console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score); 23. stu2.sayHi(); 25. var stu3 = new Student("66"); 26. console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score); 27. stu3.sayHi();
从新调用对象的属性进行从新赋值,十分的麻烦,而且使咱们上边的new Person("小三", 18, "男", "58kg");初始化失去了意义。
1. var stu3 = new Student("66"); 2. stu3.name = "小红"; 3. stu3.age = 16; 4. stu3.sex = "女"; 5. stu3.weight = "45kg"; 6. console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score); 7. stu3.sayHi();
如何解决上边的问题呢?答案是借用构造函数实现继承。
继承的时候,不扭转原型的指向,间接调用父级的构造函数来为属性赋值,即把要继承的父级的构造函数拿过去,借用一下为属性赋值,这叫做借用构造函数。借用构造函数须要应用call ()这个办法,我会在后边的文章中进行解说,大家在这里先记住用法就行了。
1. function Person(name, age, sex, weight) { 2. this.name = name; 3. this.age = age; 4. this.sex = sex; 5. this.weight = weight; 6. } 7. Person.prototype.sayHi = function() { 8. console.log("你好帅呀!!!"); 9. }; 11. function Student(name, age, sex, weight, score) { 12. //借用构造函数 13. Person.call(this, name, age, sex, weight); 14. this.score = score; 15. } 16. var stu1 = new Student("小三", 16, "男", "50kg", "110"); 17. console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score); 19. var stu2 = new Student("小红", 22, "女", "45kg", "88"); 20. console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score); 22. var stu3 = new Student("小舞", 16, "女", "40kg", "100"); 23. console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
借用构造函数继承,解决了继承的时候属性反复的问题。然而这又导致一个问题即父类中的原型办法不能被继承。
1. function Person(name, age, sex, weight) { 2. this.name = name; 3. this.age = age; 4. this.sex = sex; 5. this.weight = weight; 6. this.eat = function() { 7. console.log('吃吃吃!!!'); 8. } 9. } 10. Person.prototype.sayHi = function() { 11. console.log("你好帅呀!!!"); 12. }; 14. function Student(name, age, sex, weight, score) { 15. //借用构造函数 16. Person.call(this, name, age, sex, weight); 17. this.score = score; 18. } 19. var stu = new Student("小舞", 16, "女", "40kg", "100"); 20. console.log(stu.name, stu.age, stu.sex, stu.weight, stu.score); 21. stu.eat(); 22. stu.sayHi(); // >报错 stu.sayHi is not a function
无论是独自应用原型链继承,还是独自应用借用构造函数继承,都有很大的毛病,最好的方法是,将两者联合一起应用,施展各自的劣势,这就是咱们上面要讲的组合继承。
2.5 组合继承
原型继承和借用构造函数继承都存在各自的毛病,咱们能够将这二者联合到一起,从而施展二者之长。即在继承过程中,既能够保障每个实例都有它本人的属性,又能做到对一些属性和办法的复用。这时组合继承应运而生,组合继承=原型继承+借用构造函数继承。
1. function Person(name, age, sex) { 2. this.name = name; 3. this.age = age; 4. this.sex = sex; 5. } 6. Person.prototype.sayHi = function() { 7. console.log("你好帅呀!!!"); 8. }; 10. function Student(name, age, sex, score) { 11. //借用构造函数:解决属性值反复的问题 12. Person.call(this, name, age, sex); 13. this.score = score; 14. } 15. //扭转原型指向---原型继承解决原型办法不能被继承问题 16. Student.prototype = new Person(); //不传值 17. Student.prototype.eat = function() { 18. console.log("吃吃吃!!!"); 19. }; 20. var stu = new Student("小三", 16, "男", "111分"); 21. console.log(stu.name, stu.age, stu.sex, stu.score); 22. stu.sayHi(); 23. stu.eat(); 24. var stu2 = new Student("小舞", 15, "女", "1111分"); 25. console.log(stu2.name, stu2.age, stu2.sex, stu2.score); 26. stu2.sayHi(); 27. stu2.eat();
2.6 另一种继承形式:拷贝继承(for-in)
拷贝继承:把一个对象中的属性或者办法间接复制到另一个对象中。
1. function Person() {} 2. Person.prototype.name = "小三"; 3. Person.prototype.age = 18; 4. Person.prototype.sex = "男"; 5. Person.prototype.height = 100; 6. Person.prototype.play = function() { 7. console.log("玩的好开心呀!!!????"); 8. }; 9. var obj = {}; 10. // Person中有原型prototype,prototype就是一个对象,那么外面,name,age,sex,height,play都是该对象中的属性或者办法 11. // 新对象obj通过拷贝Person中原型prototype对象中的属性和办法继承Person中原型prototype对象的属性和办法 12. for (var key in Person.prototype) { 13. obj[key] = Person.prototype[key]; 14. } 15. console.dir(obj); 16. obj.play();
3 总结
原型链是一种关系,是实例对象和原型对象之间的关系,这种关系是通过原型(proto)来分割的。继承是类与类之间的关系,js不是面向对象的语言,没有类但能够通过函数模仿类,模仿面向对象中的继承。模仿继承是为了实现数据共享,节俭内存空间。
JS中的继承形式:
1 原型继承:通过扭转原型的指向实现继承。
2 借用构造函数继承:次要解决属性反复的问题,会导致父类中的原型办法不能继承。
3 组合继承:原型继承+借用构造函数继承,既能解决属性反复问题,又能解决办法不能被继承的问题。
4 拷贝继承:把对象中须要共享的属性或办法,间接通过遍历的形式复制到另一个对象中。
小伙伴们明天的学习就到这里了,你能够应用明天学习的技巧来改善一下你已经的代码,如果想持续进步,欢送关注我,每天学习提高一点点,就是当先的开始。