构造函数创建对象

  1. function Person() {
  2. }
  3. var person = new Person();
  4. person.name = 'Kevin';
  5. console.log(person.name) // Kevin

prototype

每个函数都有一个 prototype 属性, 其实 函数的 prototype属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个就是我们说的原型。每个对象都会从原型继承属性。
image.png

proto

每一个JavaScript对象(除了null)都具有的一个属性,叫做 **proto**这个属性会指向该对象的原型。
image.png

constructor

每个原型都有一个constructor属性指向关联的构造函数。
image.png

实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查找不到,就出找原型的原型,一直找到最顶层为止。

原型的原型

原型对象就是通过 Ojbect构造函数生成的,结合之前所讲,实例的proto指向构造函数的prototype。
image.png

原型链

image.png

补充

constructor

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.constructor === Person); // true

当获取 person.constructor时,其实person中没有 constructor属性,当不能读取到constructor属性时,会从person的原型也就是 Person.prototype中读取,正好原型中有该属性。

proto

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并存在于Person.prototype中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。

真的是继承吗?

继承意味着复制操作,然而JavaScript默认并不会复制对象的属性,相反,JavaScript只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

继承实现:探究JS常见的6种继承方式

继承概念的探究

继承可以使得子类别具有父类的各种方法和属性, 也可以重写或者覆盖某些属性和方法,使其获得父类不同的方法。

JS实现继承的几种方式

第一种:原型链继承

每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,实例则包含一个原型对象的指针。

  1. function Parent1() {
  2. this.name = 'parent1';
  3. this.play = [1, 2, 3]
  4. }
  5. function Child1() {
  6. this.type = 'child2';
  7. }
  8. Child1.prototype = new Parent1();
  9. console.log(new Child1());

有原型属性共享问题。

第二种:构造函数继承(借助call)

  1. function Parent1(){
  2. this.name = 'parent1';
  3. }
  4. Parent1.prototype.getName = function () {
  5. return this.name;
  6. }
  7. function Child1(){
  8. Parent1.call(this);
  9. this.type = 'child1'
  10. }
  11. let child = new Child1();
  12. console.log(child); // 没问题
  13. console.log(child.getName()); // 会报错

只能继承父类的实例属性和方法,不能继承原型属性或者方法。

第三种:组合继承(原型与借助call)

  1. function Parent3 () {
  2. this.name = 'parent3';
  3. this.play = [1, 2, 3];
  4. }
  5. Parent3.prototype.getName = function () {
  6. return this.name;
  7. }
  8. function Child3() {
  9. // 第二次调用 Parent3()
  10. Parent3.call(this);
  11. this.type = 'child3';
  12. }
  13. // 第一次调用 Parent3()
  14. Child3.prototype = new Parent3();
  15. // 手动挂上构造器,指向自己的构造函数
  16. Child3.prototype.constructor = Child3;
  17. var s3 = new Child3();
  18. var s4 = new Child3();
  19. s3.play.push(4);
  20. console.log(s3.play, s4.play); // 不互相影响
  21. console.log(s3.getName()); // 正常输出'parent3'
  22. console.log(s4.getName()); // 正常输出'parent3'

Parent3执行了两次。

第四种:原型式继承

Object.create方法。参数一 作为新对象原型的对象, 为新对象定义额外属性的对象(可选)
多个实例的引用类型属性指向相同的内存,存在篡改的可能。

寄生式继承

  1. let parent5 = {
  2. name: "parent5",
  3. friends: ["p1", "p2", "p3"],
  4. getName: function() {
  5. return this.name;
  6. }
  7. };
  8. function clone(original) {
  9. let clone = Object.create(original);
  10. clone.getFriends = function() {
  11. return this.friends;
  12. };
  13. return clone;
  14. }
  15. let person5 = clone(parent5);
  16. console.log(person5.getName());
  17. console.log(person5.getFriends());

寄生组合式继承

  1. function clone (parent, child) {
  2. // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  3. child.prototype = Object.create(parent.prototype);
  4. child.prototype.constructor = child;
  5. }
  6. function Parent6() {
  7. this.name = 'parent6';
  8. this.play = [1, 2, 3];
  9. }
  10. Parent6.prototype.getName = function () {
  11. return this.name;
  12. }
  13. function Child6() {
  14. Parent6.call(this);
  15. this.friends = 'child5';
  16. }
  17. clone(Parent6, Child6);
  18. Child6.prototype.getFriends = function () {
  19. return this.friends;
  20. }
  21. let person6 = new Child6();
  22. console.log(person6);
  23. console.log(person6.getName());
  24. console.log(person6.getFriends());

ES6的extends关键字实现逻辑

采用的也是寄生组合继承方式

总结

image.png