javascript - 继承与原型链

306 阅读3分钟

背景

看到继承与原型链就会想到prototype__proto__,作为一个菜鸟,在昨天之前只用过prototype,知道有一个叫__proto__的东西,但是不知道是做什么的。 但是昨天遇到一个vue的继承问题,通过instanceof 判断不出来变量是不是通过Vue.extend生成子类,代码如下:

let component = Vue.extend({
    mounted(){
        console.log('mounted')
    }
})

console.log(component instanceof Vue) // false

vuejs官方文档是这么介绍vue.extend这个方法的

既然是"子类"为什么instanceof的结果是false。

带着这个问题,开始深入学习一下js的继承与原型链。

学习

说到继承与原型链,不得不先放张图镇楼

这张图看不懂没关系,一步一步慢慢来。

先看下面代码

// 声明一个类
function Test(){
    this.a = 1
    this.b = 2
}
// 实例化
let ins = new Test()
// 原型链上追加属性
Test.prototype.b = 3
Test.prototype.c = 4

console.log(ins.a) // 1
// 这里的a是实例上的属性
console.log(ins.b) // 2
// 这里的b是实例上的属性
console.log(ins.c) // 4
// 这里的c是原型链上的属性

依次打印这几个变量,会有以下这样的结果

-> console.log(ins)
{
  "a": 1,
  "b": 2,
  "__proto__": {
    "b": 3,
    "c": 4,
    "__proto__": {
      "hasOwnProperty": function
    }
  }
}
-> console.log(Test)
{
    "arguments": null,
    "caller": null,
    "length": 0,
    "name": "Test",
    "prototype": {
        "b": 3,
        "c": 4,
        "__proto__": {
          "hasOwnProperty": function
        }
    }
}
console.log(ins.__proto__ === Test.prototype) // true

这就很明确了,ins.__proto__指向了Test.prototype。通过查看MDN,通过new生成一个实例的过程是这样的:

// js代码
var o = new Foo();

// js实际执行代码
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);

这段代码说明让我产生了极大的兴趣,下面是梳理ecma官方标准

当执行new操作的时候会执行以下步骤:

  1. Assert: constructExpr is either a NewExpression or a MemberExpression.
  2. Assert: arguments is either empty or an Arguments.
  3. Let ref be the result of evaluating constructExpr.
  4. Let constructor be ? GetValue(ref).
  5. If arguments is empty, let argList be a new empty List.
  6. Else,
  7. Let argList be ArgumentListEvaluation of arguments.
  8. ReturnIfAbrupt(argList).
  9. If IsConstructor(constructor) is false, throw a TypeError exception.
  10. Return ? Construct(constructor, argList).

最后一步的Construct是这么定义的: Construct ( F [ , argumentsList [ , newTarget ]] )

  1. If newTarget is not present, set newTarget to F.
  2. If argumentsList is not present, set argumentsList to a new empty List.
  3. Assert: IsConstructor(F) is true.
  4. Assert: IsConstructor(newTarget) is true.
  5. Return ? F.[[Construct]](argumentsList, newTarget).

之后调用了内部方法[[construct]]

  1. Assert: F is an ECMAScript function object.
  2. Assert: Type(newTarget) is Object.
  3. Let callerContext be the running execution context.
  4. Let kind be F.[[ConstructorKind]].
  5. If kind is "base", then
    • Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%ObjectPrototype%").
  6. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
  7. Assert: calleeContext is now the running execution context.
  8. If kind is "base", perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
  9. Let constructorEnv be the LexicalEnvironment of calleeContext.
  10. Let envRec be constructorEnv's EnvironmentRecord.
  11. Let result be OrdinaryCallEvaluateBody(F, argumentsList).
  12. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
  13. If result.[[Type]] is return, then
    • If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
    • If kind is "base", return NormalCompletion(thisArgument).
    • If result.[[Value]] is not undefined, throw a TypeError exception.
  14. Else, ReturnIfAbrupt(result).
  15. Return ? envRec.GetThisBinding().

其中第5步,会创建对象,调用OrdinaryCreateFromConstructor,其定义如下:
OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] )

  1. Assert: intrinsicDefaultProto is a String value that is this specification's name of an intrinsic object. The corresponding object must be an intrinsic that is intended to be used as the [[Prototype]] value of an object.
  2. Let proto be ? GetPrototypeFromConstructor(constructor, intrinsicDefaultProto).
  3. Return ObjectCreate(proto, internalSlotsList).

第2步,获取原型链,第三部创建对象 ObjectCreate ( proto [ , internalSlotsList ] )

  1. If internalSlotsList is not present, set internalSlotsList to a new empty List. Let obj be a newly created object with an internal slot for each name in internalSlotsList. Set obj's essential internal methods to the default ordinary object definitions specified in 9.1.
  2. Set obj.[[Prototype]] to proto.
  3. Set obj.[[Extensible]] to true.
  4. Return obj.

哈哈哈,没时间整理了,官方标准搬运工,有兴趣的自己看官方标准www.ecma-international.org

最后放一个官方关于prototype的说明