Published on

this、原型链与构造过程

this:动态的执行上下文指针

this 是一个特殊的关键字,它的值并不是在函数定义时确定,而是在函数被调用时根据其“调用方式”来确定的。我们可以遵循一套优先级规则来判断 this 的指向。

箭头函数

箭头函数是例外,它没有自己的 this,它的 this 是在定义时由其外层词法作用域决定的,且不可更改。

对于普通函数,this 的绑定规则优先级如下:

new 绑定 (优先级最高)

当函数作为构造函数通过 new 关键字调用时,this 会被绑定到新创建的对象实例上。

function Person(name) {
  // 这里的 this 指向新创建的 p1 对象
  this.name = name;
}
const p1 = new Person('Alice');

显式绑定 (Explicit Binding)

通过 call(), apply(), 或 bind() 方法可以明确地、强制地指定函数内部的 this 指向。

function greet() {
  console.log('Hello, ' + this.name);
}
const user = { name: 'Bob' };
greet.call(user); // 输出: Hello, Bob

隐式绑定 (Implicit Binding)

这是最常见的场景。当函数作为对象的一个方法被调用时,this 指向调用该方法的对象本身(点 . 左边的对象)。

const user = {
  name: 'Charlie',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};
user.greet(); // greet 的 this 是 user 对象
隐式绑定的一个常见陷阱:“this 丢失”

如果一个对象的方法被当作回调函数传递给另一个函数,那么它与原始对象的隐式绑定就会丢失。

根本原因:传递的只是函数本身的引用,而 this 的指向取决于其最终被调用时的上下文。当作为回调被独立调用时,它将遵循“默认绑定”规则。

function executeCallback(callback) {
  // 在这里,callback() 是独立调用的,不依赖任何对象
  callback();
}

// 将 user.greet 函数的引用传递进去
executeCallback(user.greet);
// 非严格模式输出: Hello, undefined
// 严格模式下会报错

// 解决方案:使用 .bind() 或箭头函数来固定 this
executeCallback(user.greet.bind(user)); // 正确输出: Hello, Charlie
executeCallback(() => user.greet()); // 正确输出: Hello, Charlie

默认绑定 (Default Binding) (优先级最低)

当一个函数独立调用,不符合以上任何规则时,this 的指向取决于代码是否运行在严格模式 (strict mode) 下。

  • 非严格模式: this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
  • 严格模式: this 的值是 undefined,可以防止意外污染全局变量。

原型与原型链 (__proto__ & prototype)

这是 JavaScript 实现继承的核心机制,关键在于区分两个非常相似但用途完全不同的属性。

特性 (Characteristic)__proto__prototype
拥有者几乎所有对象都有这个内部链接(技术上称为 [[Prototype]])。只有函数(非箭头函数)才有这个属性。
作用构成原型链,当试图访问一个对象的属性时,如果在该对象自身上找不到,JavaScript 引擎就会顺着 __proto__ 指向的原型(父对象)继续寻找,以此类推,直到链的末端 (null)。它的值是一个对象,被称为“原型对象”。这个对象是作为“模具”或“设计蓝图”存在的,用于存放所有由该构造函数创建的实例需要共享的方法和属性。
指向一个实例的 __proto__ 指向构造它的那个函数的 prototype 属性它默认是一个普通对象,因此它的 __proto__ 指向 Object.prototype
构造器、原型和实例的关系

函数对象的内置属性:.name

几乎所有 JavaScript 函数对象都有一个内置的、只读的 .name 属性,其值为该函数的标识符字符串。这个属性对于调试和日志记录非常有用。

function MyAwesomeFunc() {}
const f = MyAwesomeFunc;
console.log(f.name); // "MyAwesomeFunc"

// 结合上面的例子:
console.log(p1.constructor.name); // "Person"

new:调用构造函数

new 关键字是JavaScript中创建可复用对象实例的核心机制,背后蕴含着严格且自动化的标准流程:

  1. 创建新对象:在内存中创建一个全新的、空的对象。
this = {};
// 这个对象继承自 null
// Object.getPrototypeOf(this) === null
  1. 设置原型链:将这个新对象的 __proto__ 链接到构造函数的 prototype 属性。
this.__proto__ = Constructor.prototype;

// 或者使用现代标准方法
Object.setPrototypeOf(this, Constructor.prototype);

// 等价于:
// this instanceof Constructor  // true
  1. 绑定 this 上下文:在执行构造函数时,将新创建的对象作为 this 的上下文传入构造函数,使得构造函数内部对 this 的操作实际上作用于该新对象。
// 将新对象作为 this 传入构造函数
// 构造函数内的 this 指向新创建的对象
Constructor.call(this, arg1, arg2, ...);
  1. 执行构造函数代码:执行在构造函数内部编写的代码(例如 this.name = name;),为新对象添加属性和值。
// 执行构造函数内部代码
this.name = 'Alice'; 
this.age = 25;
// 这些属性都绑定到新创建的实例上
  1. 返回新对象:如果构造函数没有显式地 return 另一个对象,那么 new 操作符会自动、隐式地返回这个新创建的 this 对象。
// 如果构造函数没有显式返回对象
return this;  // 隐式返回新创建的对象

// 如果构造函数显式返回对象
return {};   // 返回指定的对象(覆盖默认行为)
return null; // 返回null,仍会返回新对象