Javascript学习笔记-原型链

Javascript学习笔记-原型链_第1张图片
Javascript原型链.png

对于Javascript原型链,是Javascript中很重要的内容,要理解关键有三点:
Javascript中原型链作用是为了实现Javascript中的继承机制。
Javascript中原型链是利用对象关联的方式实现的(不同于一般的类复制)。
Javascript中之所以能使用原型链来实现继承,关键是Javascript对象中属性检索机制
于是从类基础开始说起。

1. 面向对象

1.1 类基础

在面向对象语言中,经常会使用到类。类是一种设计模式,是某种事物的描述,类一般存在构造函数,构造函数是用于构建实例,实例是类的具体实现,类和实例的关系就类似于蓝图和建筑物的关系一样。

1.2 继承和多态

继承和多态是面向对象的两个重要的特性。对于类似Java的面向对象语言来说,继承是通过创建实例化的过程中,复制父类的属性和方法来实现,并且通过重写父类的方法(实现了多态)。

2. Javascript中对象属性

这里先提前引入原型链,主要是为了描述,具体原型链继承的方式在 3.2 原型链继承 中描述。

2.1 属性获取

Javascript的对象中属性获取的时候,会根据一定的步骤进行取值:
首先判断对象中是否存在该属性,如果存在该属性则返回属性值。
否则访问该对象的原型对象(__proto__),判断其是否存在该属性,如果存在,则返回该属性值。
否则继续遍历原型对象,直到Object.prototype如果仍然没有找到该属性则返回undefined

var o1 = {a: 1};
console.log(o1.a); // 1, obj中存在a,直接返回
function F(){};
F.prototype.b = 2;
var o2 = new F;
console.log(o2.hasOwnProperty('b')); // false
console.log(o2.b); // 2, obj的原型对象中存在属性b,返回该属性
var o3 = {};
console.log(o3.b); // undefined , 由于obj.__proto__中并不存在该属性,所以返回undefined

2.2 属性设置

属性设置也会遍历原型链,但是根据属性存在位置以及原型链上属性描述符的不同,可能会存在不同的设置结果:

// 1. 对象中存在该属性 , 直接修改属性值
var o1 = {a: 1};
console.log(o1.__proto__.a); // undefined
console.log(o1.a); // 1
o1.a = 2; 
console.log(o1.__proto__.a); // undefined ,原型链上并没有增加该属性
console.log(o1.a); // 2,当前对象属性值被修改

// 2. 对象中不存在该属性,原型链上也不存在该属性,则对象中增加该属性
var o2 = {};
console.log(o2.__proto__.a); // undefined
console.log(o2.hasOwnProperty('a')); // false
o2.a = 1;
console.log(o2.__proto__.a); // undefined ,原型链上并没有增加该属性
console.log(o2.hasOwnProperty('a')); // true

// 3. 对象中不存在该属性,原型链上存在该属性且不为只读,则当前对象增加该属性,并屏蔽原型链属性值
function F() {}
F.prototype.a = 1;
var o3 = new F;
console.log(o3.__proto__.a); // 1
console.log(o3.hasOwnProperty('a')); // false
o3.a = 2;
console.log(o3.__proto__.a); // 1, 原型链上属性没有发生变化
console.log(o3.hasOwnProperty('a')); // true,对象增加属性
console.log(o3.a); // 2,屏蔽原型链上属性

// 4. 对象中不存在该属性,原型链上该属性为只读,则不会在当前对象中增加该属性,且该属性值不变
function F() {}
Object.defineProperty(F.prototype, 'a', {
    writable: false,
    value: 1
})
var o4 = new F;
console.log(o4.__proto__.a); // 1
console.log(o4.hasOwnProperty('a')); // false
o4.a = 2;
console.log(o4.__proto__.a); // 1, 原型链上属性没有发生变化
console.log(o4.hasOwnProperty('a')); // false,对象中没有增加该属性值
console.log(o4.a); // 1,获取原型链上属性

// 5. 对象中不存在该属性,原型链上存在该属性的setter方法,则会调用该setter方法
function F() {}
Object.defineProperty(F.prototype, 'a', {
    set(){
        console.log('set a');
    }
})
var o5 = new F;
console.log(o5.__proto__.a); // undefined
console.log(o5.hasOwnProperty('a')); // false
o5.a = 2; // 'set a'
console.log(o5.__proto__.a); // undefined, 原型链上属性没有发生变化
console.log(o5.hasOwnProperty('a')); // false,对象中没有增加该属性值
console.log(o5.a); // unedefined,获取原型链上属性值并没有变

3. 继承

3.1 混入

1.2 继承和多态 中所说,一般面向对象编程语言的继承都是在对象实例化的时候,采用复制的方式将父类的内容深度复制一份到实例中,由于Javascript中并没有实例化的过程,但是可以通过对象复制的方式来实现继承关系,这样的方式可以叫做混入。
一般的显示混入,在混合对象的过程中,会将目标对象中不存在的属性进行复制添加。

  // 显示混入函数
  function mixin(target, source) {
    for(let key in source){
      if(!target.hasOwnProperty(key)){  // 不存在该属性,则添加该属性
        target[key] = source[key];
      }
    }
  }

这样就可以将源对象的属性添加到目标对象属性中,类似复制的原理实现继承关系
当然也可以直接创建一个对象包含所有属性,用新对象属性覆盖掉原对象属性并返回。

3.2 原型继承

3.2.1 Function.prototypeObject.__proto__

Javascript中默认的继承机制并没有使用类似上面的复制机制实现,而是利用Javascript中的对象,通过对象关联的方式进行实现继承,也就是原型继承。
首先Javascript的Function对象中,默认包含一个不可枚举的prototype属性,该属性的值为对应的原型对象,其结果为包含一个不可枚举属性constructor的对象

function F() {}
console.log(F.hasOwnProperty('prototype')); // true
console.log(Object.propertyIsEnumerable(F.prototype)); // false
console.log(F.prototoype); // { constructor: f}

注意:这里我们可以使用new F的方式创建对象,这种方式类似Java等面向对象语言中的实例化,F类似构造函数,但是Javascript中不存在类,所以这只能理解为构造函数方法调用。且这里的constructor并不代表对象的构造关系。
Javascript对象中存在__proto__属性,指向对象的原型对象

function F() {};
var f = new F();
console.log(f.__proto__ === F.prototype); // true

3.2.2 Javascript原型继承实现

原型继承利用了Object.create()方法实现

function F() {}
F.prototype.a = 1;
function G() {}
G.prototype = Object.create(F.prototype);
var g = new G;
console.log(g.a); // 1,根据原型链机制获取到原型对象F.protoype中属性'a'的值
console.log(g.__proto__ === G.prototype); // true
console.log(g.__proto__.__proto__ === F.prototype); // true

其中,Object.create()的实现原理:

function create(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

通过代码我们可以看到,利用Object.create()关联了对象,使得GF联系了起来,同样通过这个就可以知道为什么Object.create(null)创建出来的对象对象不在Object.prototype链上了

3.3 行为委托

对于Function的对象,利用Function.prototype原型链,创建了对象的关联,对于两个对象之间,根据Object.create()的原理,也可以直接创建关联,通过这样的方法,在属性获取不到的时候,当前对象会委托关联对象进行数据获取。

var o1 = {a: 1};
var o2 = Object.create(o1);
console.log(o2.a); //1 , 获取o1对象上的值

因为Javascript中继承本身就是对象之间的关联,所以比起使用原型继承的方式,需要使用new等看起来像构造函数的方式实现继承关系,利用行为委托更优秀。Javascript继承实现的本质对象关联

// 原型链实现继承,通常子类含有父类相同方法并进行重写
function F(width, height){
  this.width = width;
  this.height = height;
}
F.prototype.width = 1
F.prototype.height = 1;
F.prototype.square = function () {
  return this.width * this.height
}
function G(width, height){
    F.call(this, width, height); // 需要使用这种显示的方式来调用父类构造器实现初始化
}
G.prototype = Object.create(F.prototype);
G.prototype.square = function () {
  return 1/2 * this.width * this.height
}
var g = new G(2, 1);
g.square(); // 1;

// 行为委托实现继承,子对象和父对象方法名一般不同
var F = {
  init(width, height) {
    this.width = width;
    this.height = height;
  },
  rectSquare() {
    return this.width * this.height
  }
}
var G = Object.create(F);
G.build = function(width, height){
  this.init(width, height); // 利用this来实例化对象
};
G.angelSquare = function() {
  return 1/2 * this.width * this.height;
}
G.build(2, 1);
G.angelSquare(); // 1;

4. 其他

4.1 ES6中class

ES6中引入了class关键字和extends关键字来实现Javascript中的继承,使得看起来更像一般的面向对象语言,但是实际上这里的class只是原型继承的语法糖,本质还是对象的关联,并非类的复制,所以当改变原型对象的内容会影响到对应的对象。

class F {
  constructor(name) {
    this.name = name;
  }
  log() {
    return 'log:' + this.a
  }
}
var f = new F('patrick');
F.prototype.log = function () {
  return 'new log:' + this.name
}
console.log(f.log()); // new log: patrick 修改了原型对象,影响了实际对象。

4.2 hasOwnProperty

由于我们所说的Javascript对象中属性获取和设置是需要在原型链上进行查找的,所以使用hasOwnProperty值来判断是否为当前对象属性,可以阻断原型链上的查找,急速性能

4.3 Object.prototype

对于所有的对象,最终原型对象都指向Object.prototype,而且Object.prototype的原型对象为null

5. 参考:

《你不知道的Javascript(上篇)》
MDN Inheritance and the prototype chain

你可能感兴趣的:(Javascript学习笔记-原型链)