不要乱用原型创建对象了

83 阅读2分钟

创建对象和生成原型链 的正确打开方式

使用语法结构创建的对象

var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性;
//因此 o 继承了 Object.prototype 的 hasOwnProperty
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于 Array.prototype
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind 等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

使用构造器创建的对象

在 JavaScript 中,构造器其实就是一个普通的函数。当使用 [new 操作符] 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。

使用 Object.create 创建的对象

ECMAScript 5 中引入了一个新方法:[Object.create()]。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

使用 class 关键字创建的对象

ECMAScript6 引入了一套新的关键字用来实现 [class]。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。

```javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log('My name is ' + this.name);
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
  sayColor() {
    console.log('My color is ' + this.color);
  }
}

const garfield = new Cat('Garfield', 'orange');

在上述代码中,使用 class 关键字定义了 Animal 和 Cat 两个类,分别用来创建 Animal 和 Cat 对象。通过使用 extends 关键字实现了 Cat 对象继承 Animal 对象的属性和方法。

性能

在遍历原型链上查找属性比较耗时,对性能有副作用。

但是hasOwnProperty 是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。

注意:检查属性是否为 [undefined] 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined

错误实践:扩展原生对象的原型

经常使用的一个错误实践是扩展 Object.prototype 或其他内置原型。

这种技术被称为猴子补丁并且会破坏封装。尽管一些流行的框架(如 Prototype.js)在使用该技术,但仍然没有足够好的理由使用附加的非标准方法来混入内置原型。

扩展内置原型的唯一理由是支持 JavaScript 引擎的新特性,创建 polyfill,本质上为老版本浏览器缺失的方法提供自己的实现,该方法是由 JavaScript 规范定义的。