ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.
ECMAScript是一种面向对象语言,支持基于原型的委托式继承。

原型链

在 es5 中 new 后面的是构造函数,对应 constructor
构造函数的属性prototype 是原型

  1. var a = new A(); 做了:
  2. // 1. 首先创建一个空对象
  3. var o = new Object();
  4. // 2. 将空对象的原型赋值为构造器函数的原型
  5. o.__proto__ = A.prototype;
  6. // 3. 更改构造器函数内部this,将其指向新创建的空对象
  7. A.call(o);

最后当然是返回了。返回的时候会进行一个判断,如果构造器函数(这里即A)设置了返回值,并且返回值是一个Object类型的话,就直接返回该Object,否则返回新创建的空对象(这里即o);

  1. // Persion是函数,Persion在new调用的时候变成构造函数
  2. function Persion (name) {
  3. // name 是构造参数
  4. this.name = name;
  5. this.showName = function () {
  6. console.log(this.name);
  7. }
  8. /
  9. }
  10. var me = new Persion('zbj');
  11. var other = new Persion('yqy');
  12. me.showName();
  13. other.showName();

上面构造函数生成的每个实例都有各自的showName方法的副本,
这不仅无法做到数据共享,也是极大的资源浪费。
实现共享方法,加入 prototype

  1. function Persion (name) {
  2. this.name = name;
  3. }
  4. // Persion是函数
  5. // Persion.prototype 是函数自定义属性
  6. // Persion.prototype 指向的是 原型对象
  7. // 可以理解
  8. // persion.prototype有constructor和__proto__(指向Object.prototype)两个属性
  9. Persion.prototype.showName = function () {
  10. console.log(this.name);
  11. }
  12. // me有name和showName成员变量
  13. var me = new Persion('zbj');
  14. me.showName();
  15. // me.constructor 指向构造函数
  16. me.constructor.prototype.showName = function () {
  17. console.log('error')
  18. }
  1. // 对象有个__proto__指向原型对象
  2. me.__proto__ === Persion.prototype // true
  3. // 原型对象有个 constructor 指向构造函数
  4. me.__proto__.constructor === me.constructor // true
  5. me.constructor === Pserson // true
  6. Persion.prototype.constructor === Persion
  7. // 构造函数有个 prototype 指向原型对象
  8. // 原型对象也有一个__proto__

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。顺着链条去找
如果改了prototype,要把prototype的constructor改回来

继承

js基于原型链的实现继承

继承的实现原理

proto
proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的JS引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

构造函数的继承

构造函数绑定

  1.   function Animal(){
  2.     this.species = "动物";
  3.   }
  4. Animal.prototype.eat = function () {
  5. console.log("amimal eat")
  6. }
  7.   function Cat(name,color){
  8.     Animal.apply(this, arguments);
  9.     this.name = name;
  10.     this.color = color;
  11.   }
  12.   var cat1 = new Cat("大毛","黄色");
  13.   alert(cat1.species); // 动物
  14. cat1.eat() // typeError

Cat 没有继承 prototype

prototype模式

  1.  function Cat(name,color){
  2.     this.name = name;
  3.     this.color = color;
  4.  }
  5.  Cat.prototype = new Animal();
  6.   Cat.prototype.constructor = Cat;
  7.   var cat1 = new Cat("大毛","黄色");
  8. cat1.eat()// animal eat

缺点是,继承了所有的实例,可能只想继承原型

直接继承prototype

  1.  function Cat(name,color){
  2.     this.name = name;
  3.     this.color = color;
  4.  }
  5. // Cat 和 Animal指向了同一个原型
  6.   Cat.prototype = Animal.prototype;
  7. // 实际上把 Animal.prototype.constructor 也指向了 Cat
  8.   Cat.prototype.constructor = Cat;
  9.   var cat1 = new Cat("大毛","黄色");
  10.   alert(cat1.species); // error
  11. cat1.eat() // animal eat

缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

利用空对象作为中介

  1.   var F = function(){};
  2.   F.prototype = Animal.prototype;
  3.   Cat.prototype = new F();
  4.   Cat.prototype.constructor = Cat;

使用了第二种方法,但是避开了缺点

封装成一个方法

  1.   function extend(Child, Parent) {
  2.     var F = function(){};
  3.     F.prototype = Parent.prototype;
  4.     Child.prototype = new F();
  5.     Child.prototype.constructor = Child;
  6.     Child.uber = Parent.prototype;
  7.   }
  8. extend(Cat,Animal);
  9.   var cat1 = new Cat("大毛","黄色");
  10.   alert(cat1.species); // 动物

拷贝继承

  1.   function extend2(Child, Parent) {
  2.     var p = Parent.prototype;
  3.     var c = Child.prototype;
  4.     for (var i in p) {
  5.       c[i] = p[i];
  6.       }
  7.     c.uber = p;
  8.   }
  9. extend2(Cat, Animal);
  10.   var cat1 = new Cat("大毛","黄色");
  11.   alert(cat1.species); // 动物

对象的继承

理解原型链

  • 原型链的方法
    Object.create() 参数:proto
    新创建对象的原型对象。
    propertiesObject
  1. Object.create = function (proto,propertiesObject) {
  2. function F() {}
  3. F.prototype = proto;
  4. return new F();
  5. }
  1. const person = {
  2. isHuman: false,
  3. printIntroduction: function () {
  4. console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  5. }
  6. };
  7. const me = Object.create(person);
  8. me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
  9. me.isHuman = true; // inherited properties can be overwritten
  10. me.printIntroduction();
  11. // expected output: "My name is Matthew. Am I human? true"
  • 浅拷贝
  1.   function extendCopy(p) {
  2.     var c = {};
  3.     for (var i in p) {
  4.       c[i] = p[i];
  5.     }
  6.     c.uber = p;
  7.     return c;
  8.   }

但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

  • 深拷贝
    所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用”浅拷贝”就行了。
  1.   function deepCopy(p, c) {
  2.     var c = c || {};
  3.     for (var i in p) {
  4.       if (typeof p[i] === 'object') {
  5.         c[i] = (p[i].constructor === Array) ? [] : {};
  6.         deepCopy(p[i], c[i]);
  7.       } else {
  8.          c[i] = p[i];
  9.       }
  10.     }
  11.     return c;
  12.   }
  13.  var Doctor = deepCopy(Chinese);
  14. Chinese.birthPlaces = ['北京','上海','香港'];
  15.   Doctor.birthPlaces.push('厦门');
  16. alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
  17.   alert(Chinese.birthPlaces); //北京, 上海, 香港