作用域 & 作用域链

作用域: 就是变量或者是函数能作用的范围。JS 没有块级作用域,只有函数和全局作用域。 作用域链: 就是一层一层向父层寻找没有定义的变量。

作用域

ES5 有”全局作用域“和”函数作用域“。ES6 的letconst使得 JS 用了”块级作用域“。为了解决 ES5 的全局冲突,一般都是闭包(立即执行函数),将变量封装到函数作用域。

作用域链

当前作用域没有找到定义,继续向父级作用域寻找,直至全局作用域。这种层级关系,就是作用域链

作用域链的终点是全局对象 window

原型 & 原型链

原型链的基本原理:任何一个实例,通过原型链,找到它上面的原型,该原型对象中的方法和属性,可以被所有的原型实例共享。原型链就是多个对象通过 __proto__ 的方式连接了起来。

const obj = {};
// 引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype); // output: true
function ha() {}
// 构造函数的原型上有一个 constructor 指向该构造函数自身
ha.prototype.constructor === ha; // true
// 构造函数的原型也是一个对象,其 __proto__ 属性指向它的构造函数 Object 的原型
ha.prototype.__proto__ === Object.prototype; // true
// `ha`这个普通的函数,也是一个对象(引用类型),是`Function`构造函数的一个实例
ha.__proto__ === Function.prototype;

Function.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null;

// `instance.constructor.prototype = instance.__proto__`
const a = new ha();
a.constructor === ha; // true
a.constructor.prototype === a.__proto__; // true
Function.__proto__ == Object.prototype; //false
Function.__proto__ == Function.prototype; //true
  • 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象。
  • 所有的函数(除了箭头函数),都有一个 prototype 属性,属性值也是一个普通的对象。
  • 所有的引用类型(数组、对象、函数)的__proto__属性值指向它的构造函数的 prototype 属性值。

ES6 的箭头函数没有 prototype 属性,但是有__proto__属性。 除了 Object.prototype 这个对象,其他所有的对象都会有__proto__属性(函数也是对象)

总结:

  • 于是所有的函数的原型都是 Function.prototype (即 (function(){}).__proto__ === Function.prototype)
  • 访问一个对象的属性时, 如果从这个对象里找不到, 就从 obj.__proto__里找, 再找不到就继续从 obj.__proto__.__proto__里找, 最终会容到达 Object.prototype
  • 构造函数的 prototype,默认情况下就是一个 new Object()还额外添加了一个 constructor 属性.
  • 函数有的__proto__是因为函数是 Function 的实例对象.
  • 构造函数默认自带有一个 prototype 属性,prototype 对象中有 constructor 属性,(除了 Function.prototype.bind())这个属性指向这个构造函数。
  • Object 是所有对象的父级,所有对象都可以通过 __proto__ 找到它。几乎所有的 js 对象都是位于原型链顶端的 Object 实例
  • Function 是所有函数的父级,所有函数都可以通过 __proto__ 找到它
  • 原型链的顶端是 Object.prototype.__proto__ 也就是 null

instanceof

instanceof 可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。instanceof是通过原型链来进行判断的,所以只要不断地通过访问__proto__,就可以拿到构造函数的原型prototype, 直到null停止。

instanceof原理:判断实例对象的__proto__属性,和构造函数的prototype属性,是否为同一个引用(是否指向同一个地址)。

function instanceof(left, right) {
  // 获得类型的显示原型
  let prototype = right.prototype;
  // 获得对象的隐式原型
  left = left.__proto__;
  // 判断对象的类型是否等于类型的原型
  while (true) {
    // 直到对象原型为 `null`,因为原型链最终为 `null`
    if (left === null) return false;
    if (prototype === left) return true;
    left = left.__proto__;
  }
}

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

const Person = function () {};
const p1 = new Person();
p1 instanceof Person; // true

const str = "hello world";
str instanceof String; // false

const str1 = new String("hello world");
str1 instanceof String; // true

比如说:

foo instanceof Foo的结果为 true,因为foo.__proto__ === Foo.prototypetrue

foo instanceof Object的结果也为 true,为Foo.prototype.__proto__ === Object.prototypetrue

所以不能轻易的说:foo 一定是 由Object创建的实例`。这句话是错误的。

instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

使用 instanceof判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

[] instanceof Array; // true
[] instanceof Object; // true

判断对象是哪个类的直接实例

已知 A 继承了 B,B 继承了 C。怎么判断 a 是由 A直接生成的实例,还是 B 直接生成的实例呢?还是 C 直接生成的实例呢?

分析:这就要用到原型的constructor属性了。

foo.__proto__.constructor === M的结果为true,但是 foo.__proto__.constructor === Object的结果为false

所以,用 constructor判断就比用 instanceof判断,更为严谨。

function A() {}
const a = new A();
a.__proto__ === A.prototype; // true
A.prototype.constructor === A; // true
a.__proto__.constructor === A; // true
a.constructor === A; // true
A.constructor === Function; // true
Last Updated:
Contributors: yiliang114