JavaScript之原型 & 原型链

640 阅读3分钟

JavaScript 中万物皆对象,对象皆出自构造函数。

对象分为:函数对象和普通对象。

对象独有:__proto__ constructor

函数独有:prototype

JavaScript 中函数也是对象,故函数也拥有__proto__ constructor 属性。

var A = function(){}; // A是一个方法,当然也是个对象  
var a = new A();

原型

定义:给其它对象提供共享属性的对象。

prototype 自己也是对象,只是被用以承担了某个职能。

公式:

`实例.__proto__ === 实例的构造函数.prototype`

JavaScript 中的 ObjectFunction 就是典型的函数对象。

所有函数对象的__proto__都指向Function.prototype

原型链

原型链的形成是靠 __proto__ 而非prototype

instanceof

object instanceof constructor

instanceof 用来检测 contructor.prototype 是否在 object 的原型链上。

function instance_of(l, r) {
    var rProto = r.prototype
    var lProto = l.__proto__
    console.log(typeof rProto)
    console.log(typeof lProto)
    if (!lProto) return false
    while(l) {
        console.log(typeof lProto)
        if (lProto === rProto) {
            return true
        }
        lProto = lProto.__proto__
    }
}

解析:Object instanceof Object

rProto = Object.prototype
lProto = Object.__proto__ = Function.prototype

// 进入循环体,第一次循环
lProto !== rProto
lProto = lProto.__proto__ = Function.prototype.__proto__ = Object.prototype

// 第二次循环
lProto === rProto

解析:Function instanceof Function

继承

ES5 中的继承实现方式

new 关键字

  1. 创建对象 obj;
  2. 将对象 obj 的原型指向构造函数,使对象 obj 可以访问到构造函数原型对象的属性;
  3. 将构造函数的 this 绑定到当前对象 obj,使对象 obj 可以访问到构造函数的属性;
  4. 如果构造函数返回一个对象,那么我们返回这个对象,否则返回对象 obj。

以 new 操作符调用构造函数的时候,函数内部实际上发生以下变化:

  1. 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
  2. 属性和方法被加入到 this 引用的对象中。
  3. 新创建的对象由 this 所引用,并且最后隐式的返回 this.
function objectFactory() {
    var obj = new Object()
    
    var Constructor = [].shift.call(arguments) // 第一个参数就是构造函数
    
    function F(){}
    F.prototype = Constructor.prototype
    obj = new F()
    
    var result = Constructor.apply(obj, arguments)
    return typeof result === 'object' ? result : obj
}

类式继承

子元素.prototype.__proto__ = 父元素.prototype

function SuperClass() {
  this.superValue = true;
}
SuperClass.prototype.getSuperValue = function() {
  return this.superValue;
}

function SubClass() {
  this.subValue = false;
}
SubClass.prototype = new SuperClass();

SubClass.prototype.getSubValue = function() {
  return this.subValue;
}

var instance = new SubClass();

console.log(instance instanceof SuperClass) // true
console.log(instance instanceof SubClass) // true
console.log(SubClass instanceof SuperClass)// false

缺点:

  1. 子类通过 prototype 对父类实例化,继承了父类的属性和方法,如果父类中有引用类型的属性,会被共享。一个子类实例对共享属性的修改会对其他实例的属性造成影响。
  2. 子类的继承是靠其原型prototype属性对父类实例进行实例化的,因此不能对父类传递参数。

构造函数继承

function SuperClass() {
  this.superValue = true;
}
SuperClass.prototype.getSuperValue = function() {
  return this.superValue;
}

function SubClass() {
  SuperClass.call(this, arguments)
}

缺点:

  1. 父类的原型方法不会被继承;
  2. 如果想被继承,就需要把 prototype 放到构造函数中,这样创建的每个实例都会有一套单独的属性和方法,不能复用。

组合继承

function SuperClass() {
  this.superValue = true;
}
SuperClass.prototype.getSuperValue = function() {
  return this.superValue;
}

function SubClass() {
  SuperClass.call(this, arguments)
}

SubClass.prototype = new SuperClass()

缺点:父类构造函数被调用2次。

原型式继承

function inheritObject(o) {
    //声明一个过渡对象
    function F() { }
    //过渡对象的原型继承父对象
    F.prototype = o;
    //返回过渡对象的实例,该对象的原型继承了父对象
    return new F();
}

缺点:

  1. 原型式继承和类式继承一个样子,对于引用类型的变量,还是存在子类实例共享的情况。

寄生式继承

function CreateObj(obj) {
    var o = inheritObject(obj)
    o.method = function(){}
    return o
}

寄生组合式继承

function inheritObject(o) {
  //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例,该对象的原型继承了父对象
  return new F();
}

function inheritPrototype(subClass,superClass) {
    // 复制一份父类的原型副本到变量中
  var p = inheritObject(superClass.prototype);
  // 修正因为重写子类的原型导致子类的constructor属性被修改
  p.constructor = subClass;
  // 设置子类原型
  subClass.prototype = p;
}

ES6 中的继承实现方式-todo