六维教程

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 是构造函数加原型的语法糖。
上一篇
JavaScript 原型是什么
下一篇
JavaScript 认识 DOM