JS继承

栏目: JavaScript · 发布时间: 6年前

内容简介:The第一个题目:

prototype vs __proto__ vs constructor

The prototype is a property on a constructor function that sets what will become the __proto__ property on the constructed object.

prototype 是构造函数上的一个属性,而 __proto__ 指向原型函数的 prototype

第一个题目:

function MyConstructor() {};
(MyConstructor.__proto__ === MyConstructor.prototype); //false?! why?

MyConstructor 是一个函数(对象),其 __proto__ 应该指向 Funciton.prototype

那么 MyConstructor.__proto__ === Function.prototype 则是 true。

来看下面一个题目:

var obj = {};
var arr = [];
var fun = function() {};    
   
console.log(obj.prototype);
console.log(arr.prototype);
console.log(fun.prototype);

console.log(arr.__proto__);
console.log(fun.__proto__);

// JS 中一切皆对象,所以以下都为 object
console.log(obj.__proto__);
console.log(arr.__proto__.__proto__);
console.log(fun.__proto__.__proto__);
console.log(fun.prototype.__proto__);

JS继承

ps: In the Standard ECMA-2625.1 spec , the term [[Prototype]] is used. And that’s the same as __proto__ .

constructor:

每个原型都有一个 constructor 属性指向关联的构造函数。

function Person() {...}
console.log(Person === Person.prototype.constructor); // true

综上可以得出:

function Person() {...}
var person = new Person();

console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

JS继承

原型链:

每个实例对象(object )都有一个私有属性(称之为 proto )指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

JavaScript 对象是动态的属性“包”(指其自己的属性),它有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

因此,当你执行: var o = new Foo(); JavaScript 实际上执行的是:

var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);

(或者类似上面这样的),然后当你执行: o.someProp;
它检查o是否具有someProp属性。如果没有,它会查找 Object.getPrototypeOf(o).someProp ,如果仍旧没有,它会继续查找 Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp

Ps: 每一个函数对象(Function)都有一个 prototype 属性,并且只有函数对象有 prototype 属性,因为 prototype 本身就是定义在 Function 对象下的属性

创建对象

1. 使用语法结构

var o = {a: 1};

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

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

// 数组都继承于 Array.prototype 
// (Array.prototype 中包含 indexOf, forEach等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

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

2. 构造器 :使用 new 操作符

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

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

var g = new Graph();
// g是生成的对象,他的自身属性有'vertices'和'edges'.
// 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.

3. Object.create()

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

var foo = {one: 1, two: 2};

// bar.[[prototype]] = foo
var bar = Object.create(foo);

bar.three = 3;

bar.one; // 1
bar.two; // 2
bar.three; // 3

4. class 关键字

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

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

JS 继承

几乎所有 JavaScript 中的对象都是位于原型链顶端的Object的实例。

当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。 —《你不知道的JavaScript》

1.原型链继承

function Parent () {
    this.name = 'kevin';
}

Parent.prototype.getName = function () {
    console.log(this.name);
}

function Child () {...}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()) // kevin

问题:

  1. 引用类型的属性被所有实例共享,举个例子:

    function Parent () {
       	this.names = ['kevin', 'daisy'];
    }
    
    function Child () {...}
    Child.prototype = new Parent();
    var child1 = new Child();
    
    child1.names.push('yayu');
    console.log(child1.names); // ["kevin", "daisy", "yayu"]
    
    var child2 = new Child();
    console.log(child2.names); // ["kevin", "daisy", "yayu"]
    
  2. 在创建 Child 的实例时,不能向Parent传参

2.借用构造函数 Classical Inheritance

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]

优点:

  1. 避免了引用类型的属性被所有实例共享
  2. 可以在 Child 中向 Parent 传参,例如:
function Parent (name) {
    this.name = name;
}

function Child (name) {
    Parent.call(this, name);
}

var child1 = new Child('kevin');
console.log(child1.name); // kevin

var child2 = new Child('daisy');
console.log(child2.name); // daisy

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

3. 组合继承

原型链继承和经典继承双剑合璧。

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('kevin', '18');
child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

4. 原型式继承 Prototypal Inheritance

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1和person2有独立的 name 值,而是因为 person1.name = 'person1' ,给person1添加了 name 值,并非修改了原型上的 name 值。

person1.__proto__.name = "person1"
console.log(person2.name); // person1

Object.setPrototypeOf()

var foo = {name: "foo", one: 1, two: 2};
var bar = {two: "two", three: 3};

Object.setPrototypeOf(bar, foo); // foo is now the prototype of bar.

console.log(bar.one) // Resolves to 1.

// The child object's properties are also accessible.
console.log(bar.three) // Resolves to 3.

// Own properties shadow prototype properties
console.log(bar.two); // Resolves to "two"
console.log(bar.name); // unaffected, resolves to "foo"
console.log(foo.name); // Resolves to "foo"

5. 寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

回忆一下组合继承的代码,组合继承最大的缺点是会调用两次父构造函数。

一次是设置子类型实例的原型的时候: Child.prototype = new Parent(); 一次在创建子类型实例的时候: var child1 = new Child('kevin', '18'); 其实在这句中,我们会执行 Parent.call(this, name);

在这里,我们又会调用了一次 Parent 构造函数。

所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为 [‘red', 'blue', 'green'] 。那么我们该如何精益求精,避免这一次重复调用呢?

如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 关键的三步
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('kevin', '18');

console.log(child1);
最后我们封装一下这个继承方法:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}

// 当我们使用的时候:
prototype(Child, Parent);

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。 ——《JavaScript高级程序设计》

Reference:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Transcending CSS

Transcending CSS

Andy Clarke、Molly E. Holzschlag / New Riders / November 15, 2006 / $49.99

As the Web evolves to incorporate new standards and the latest browsers offer new possibilities for creative design, the art of creating Web sites is also changing. Few Web designers are experienced p......一起来看看 《Transcending CSS》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具