原型链
实例对象被构造函数构造出来后,其 __proto__ 会指向其构造函数的 prototype,当访问实例对象的属性时,如果有则直接访问,否则向上追溯原型的 __proto__ 中的属性,直到找到或返回 undefined 为止。
| 对比项 | __proto__ | prototype |
|---|---|---|
| 中文含义 | 原型链链接 | 原型对象 |
| 谁拥有 | 所有对象(包括函数、数组等) | 只有函数才有 |
| 存的是什么 | 存的是一个引用,指向构造函数的prototype | 存的是一个对象,里面放着共享的属性和方法 |
| 作用 | 连接实例和原型,形成原型链 | 作为“仓库”,存放所有实例共享的东西 |
this 绑定
- 普通函数调用,this 指向全局
- 函数作为对象的方法被调用时,this 指向这个对象
- 通过
call、apply、bind手动指定 this 指向谁 - 用
new调用函数时,this 指向新创建的空对象
一个例子:
js
// 一个简单的 throttle 函数(故意不加 apply)
function simpleThrottle(fn, context) {
let timer = null
return function(...args) {
if (timer) return
timer = setTimeout(() => {
fn.apply(context, args) // 把this.替换成传入的context.
timer = null
}, 100)
}
}
// 用户对象
const user = {
name: '张三',
sayHi: function(age) {
console.log(`我叫${this.name},今年${age}岁`)
}
}
// 包装一下
const throttledSayHi = simpleThrottle(user.sayHi, user)
// 调用
throttledSayHi(18)
// 输出:我叫undefined,今年18岁 ❌ this.name 是 undefined
// 因为 this 指向了 window/global,不是 user闭包
一个函数可以“记住”并访问它定义时的作用域,即使这个函数在它定义的作用域之外执行。
当内部函数引用了外部函数的变量时,即使外部函数执行完毕,JavaScript 引擎也不会销毁这个变量,因为内部函数的闭包仍然持有对该变量所在作用域的引用。这就是闭包能够“记住”外部变量的原因,也是实现私有数据的基础。
js
function outer() {
let count = 0; // 外部函数的局部变量
function inner() { // 内部函数(闭包)
count++; // 可以访问 count
console.log(count);
}
return inner; // 把内部函数返回出去
}
const fn = outer(); // outer 执行完毕,按理 count 应该消失
fn(); // 输出 1 // 但闭包让 count 仍然存在
fn(); // 输出 2ES2022 的 私有字段(#) 是语言层面提供的真正私有,和闭包模拟的私有不同:
javascript
class Example {
#privateField = 1; // 私有属性
#privateMethod() { // 私有方法
console.log('private');
}
publicMethod() {
console.log(this.#privateField);
this.#privateMethod();
}
}ES5 继承
js
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 只调用一次
this.age = age;
}
// 核心:用 Object.create 创建一个以 Parent.prototype 为原型的对象
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const c = new Child('小红', 8);
c.sayName(); // 输出: 小红箭头函数
普通函数:this 看谁调用。
箭头函数:this 看它写在哪。