- 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中创建可复用对象实例的核心机制,背后蕴含着严格且自动化的标准流程:
- 创建新对象:在内存中创建一个全新的、空的对象。
this = {};
// 这个对象继承自 null
// Object.getPrototypeOf(this) === null
- 设置原型链:将这个新对象的
__proto__链接到构造函数的prototype属性。
this.__proto__ = Constructor.prototype;
// 或者使用现代标准方法
Object.setPrototypeOf(this, Constructor.prototype);
// 等价于:
// this instanceof Constructor // true
- 绑定
this上下文:在执行构造函数时,将新创建的对象作为this的上下文传入构造函数,使得构造函数内部对this的操作实际上作用于该新对象。
// 将新对象作为 this 传入构造函数
// 构造函数内的 this 指向新创建的对象
Constructor.call(this, arg1, arg2, ...);
- 执行构造函数代码:执行在构造函数内部编写的代码(例如
this.name = name;),为新对象添加属性和值。
// 执行构造函数内部代码
this.name = 'Alice';
this.age = 25;
// 这些属性都绑定到新创建的实例上
- 返回新对象:如果构造函数没有显式地
return另一个对象,那么new操作符会自动、隐式地返回这个新创建的this对象。
// 如果构造函数没有显式返回对象
return this; // 隐式返回新创建的对象
// 如果构造函数显式返回对象
return {}; // 返回指定的对象(覆盖默认行为)
return null; // 返回null,仍会返回新对象