JavaScript 原型共享方法
上一篇讲了原型的基本规律,这一篇解决一个实际问题,对象的方法应该放在哪里。放在构造函数内部,每个实例各留一份,浪费内存。放在 prototype 上,所有实例共享一份,这才是推荐的写法。
方法放在 prototype 上
给 User.prototype 挂一个 sayHi 函数,所有实例都能调用它。
function User(name) {
this.name = name;
}
User.prototype.sayHi = function () {
console.log('你好,我是 ' + this.name);
};
const user1 = new User('张三');
const user2 = new User('李四');
user1.sayHi(); // 你好,我是 张三
user2.sayHi(); // 你好,我是 李四
注意 user1 和 user2 自己身上都没有 sayHi,它住在 User.prototype 上。调用时 this 仍然指向当前实例,所以 user1 调用打出张三,user2 调用打出李四。this 的规则可以回顾 JavaScript this 指向基础。
对比,放构造函数里 vs 放原型上
先看把方法放在构造函数内部的写法,每次 new 都会在实例上新建一份函数副本。
function User(name) {
this.name = name;
this.sayHi = function () {
console.log('你好,我是 ' + this.name);
};
}
const user1 = new User('张三');
const user2 = new User('李四');
new 出 1000 个用户,内存里就有 1000 份内容完全相同的 sayHi 函数,纯属浪费。
再看把方法放在原型上的写法,内存里只有一份。
function User(name) {
this.name = name;
}
User.prototype.sayHi = function () {
console.log('你好,我是 ' + this.name);
};
const user1 = new User('张三');
const user2 = new User('李四');
new 出 1000 个用户,内存里也只有一份 sayHi,所有实例沿原型链共享它。所以实际开发里的约定是,属性放构造函数里,因为每个实例的属性各不相同,方法放原型上,因为所有实例的方法完全一样。
验证共享
怎么证明方法真的被共享了?用全等比较两个实例的方法,放在原型上时它们指向同一个函数,结果为 true。
function User(name) {
this.name = name;
}
User.prototype.sayHi = function () {
console.log('你好,我是 ' + this.name);
};
const user1 = new User('张三');
const user2 = new User('李四');
console.log(user1.sayHi === user2.sayHi); // true,是同一个函数
对比一下放在构造函数内部的写法,每个实例各有一份函数,结果为 false。
function User(name) {
this.name = name;
this.sayHi = function () {
console.log('你好,我是 ' + this.name);
};
}
const user1 = new User('张三');
const user2 = new User('李四');
console.log(user1.sayHi === user2.sayHi); // false,是两份不同的函数
这个比较结果就是判断方法是否共享的直观证据。
原型上的方法还能拿到实例属性吗
能。sayHi 虽然住在原型上,但调用方式 user1.sayHi() 让 this 指向 user1,照样能读到 name。原型只是方法的存放地,不改变 this 的指向规则。
与 class 的关系
ES6 里 class 的写法更接近其他语言,但本质上就是构造函数加原型的语法糖,class 里写的方法等价于放在 prototype 上。这个概念在 ES6 教程里再展开,这里只要记住原型共享方法这个思路就够了。
小结
- 方法放 prototype 上,所有实例共享一份,节省内存。
- 方法放构造函数内部,每个实例各有一份副本,浪费内存。
- 用 user1.sayHi === user2.sayHi 可以验证方法是否被共享。
- 原型上的方法调用时 this 仍指向当前实例。
- ES6 的 class 是构造函数加原型的语法糖。