Skip to content

Latest commit

 

History

History
145 lines (119 loc) · 4.54 KB

07.面向对象与原型.md

File metadata and controls

145 lines (119 loc) · 4.54 KB

07.面向对象与原型

  • 探讨原型
  • 使用函数作为构造器
  • 使用原型扩展对象
  • 避免常见的问题
  • 使用继承创建类

理解原型

对象构造器与原型

function Ninja() {
  this.swung = true;
}
const ninja1 = new Ninja();
Ninja.prototype.swingSword = function() {
  return this.swung;
};
console.log(ninja1.swingSword()); // true
Ninja.prototype = {
  pierce: function() {
    return true;
  }
};
console.log(ninja1.swingSword()); // true
const ninja2 = new Ninja();
console.log(ninja2.pierce()); // true
console.log(ninja2.swingSword); // undefined

检查实例的类型与它的 constructor

function Ninja() {}
const ninja = new Ninja();
console.log(typeof ninja === 'object');
console.log(ninja instanceof Ninja);
console.log(ninja.constructor === Ninja);

实现继承

function Person(){}
Person.prototype.dance = funtion(){};

function Ninja(){}
Ninja.prototype = {
  dance: Person.prototype.dance; // 复制 Person 的原型方法 dance 到 Ninja 的原型上
};

const ninja = new Ninja();
console.log(ninja instanceof Ninja);
console.log(ninja instanceof Person);
console.log(ninja instanceof Object);

由于函数原型是对象类型,因此有多种复制功能(如属性或方法)可以实现继承的方法。这不是真正的继承——仅仅是复制。

原型实现继承

function Person() {}
Person.prototype.dance = function() {};
function Ninja() {}
Ninja.prototype = new Person();
const ninja = new Ninja();
ninja instanceof Ninja;
ninja instanceof Person;
ninja instanceof Object;
typeof ninja.dance === 'function';

直接使用 Person 的原型对象作为 Ninja 的原型,如 Ninja.prototype = Person.prototype。这样做会导致在 Person 原型上所发生的所有变化都被同步到 Ninja 原型上(Person 原型与 Ninja 原型是同一个对象),一定会有不良的副作用。

重写 constructor 属性的问题

ninja.constructor === Person; // true

解决 constructor 属性的问题

function Person() {}
Person.prototype.dance = function() {};
function Ninja() {}
Ninja.prototype = new Person();
Object.defineProperty(Ninja.prototype, 'constructor', {
  enumerable: false,
  value: Ninja,
  writable: true
});
var ninja = new Ninja();
console.log(ninja.constructor === Ninja);
for (let prop in Ninja.prototype) {
  console.log(prop === 'dance');
}

instanceof 操作符

检查操作符右边的函数的原型是否存在于操作符左边的对象的原型链上。

在 ES6 使用 JavaScript 的 class

虽然 ES6 引入关键字 class,但是底层仍然是基于原型的实现。class 只是语法糖,使得在 JavaScript 模拟类的代码更为简洁。

在 ES6 中的静态方法

class Ninja {
  constructor(name, level) {
    this.name = name;
    this.level = level;
  }
  swingSword() {
    return true;
  }
  static compare(ninja1, ninja2) {
    return ninja1.level - ninja2.level;
  }
}
var ninja1 = new Ninja('Yoshi', 4);
var ninja2 = new Ninja('Hattori', 3);
console.log(!('compare' in ninja1) && !('compare' in ninja2));
console.log(Ninja.compare(ninja1, ninja2) > 0);
console.log(!('swingSword' in Ninja));

小结

  • JavaScript 对象是属性名与属性值的集合。
  • JavaScript 使用原型。
  • 每个对象上都具有原型的引用,搜索指定的属性时,如果对象本身不存在该属性,则可以代理到原型上进行搜索。对象的原型也可以具有原型,以此类推,形成原型链。
  • 可以通过 Object.setPrototypeOf 方法定义对象的原型。
  • 原型与构造函数密切相关。每个函数都具有原型属性,该函数创建的对象的原型,就是函数本身。
  • 函数原型对象具有 constructor 属性,该属性指向函数本身。该函数创建的全部对象均访问该属性,constructor 属性还可用于判断对象是否是由指定的函数创建的。 在JavaScript中,几乎所有的内容在运行时都会发生变化,包括对象的原型和函 数的原型。
  • 如果我们希望 Ninja 构造函数创建的实例都可以“继承”(更准确地说,可以访问)Person 构造函数的属性,那么,将 Ninja 构造函数的原型设置为 Person 类的实例。
  • 在 JavaScript 中,原型具有属性(如 configurableenumerablewritable)。这些属性可通过内置的 Object.defineProperty 方法进行定义。
  • JavaScript ES6 引入关键字 class,使得我们可以更方便地实现模拟类。在底层仍然是使用原型实现的。
  • 使用 extends 可以更优雅地实现继承。