文档章节

原型与原型链.

o
 osc_4nmshwhm
发布于 2018/08/06 15:28
字数 2758
阅读 0
收藏 0

精选30+云产品,助力企业轻松上云!>>>

一、创建对象的重要模式: 原型模式

面向对象的语言可以通过类创建任意多个具有相同属性和方法的对象。ECMAScript中没有类的概念(在ES6的class之前),但是它的 对象是基于引用类型创建的,可以在一定程度上充当"类“的角色。

JavaScript创建对象最常用的方法是使用 Object构造函数(即通过new Object())或 对象字面量。但它们有个明显缺点:如果创建的一堆对象中都含有相同的方法,那么就会重复写大量重复的代码。所以会采用 工厂模式构造函数模式原型模式等设计模式来创建对象。其中 原型模式是本文需要着重分析的。

1.原型模式基本实现

Eg1:

function Person() {
}

Person.prototype.name = "Bonnie";
Person.prototype.age = 26;
Person.prototype.sayName = function() {
  console.log(this.name)
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.sayName === person2.sayName);//true

2. 原型模式相关概念理解

(1)构造函数

构造函数是专门用来创建对象的函数,其本身也是函数。构造函数始终都应该以一个大写字母开头。

要使用构造函数创建新实例,必须使用new操作符。

(2)prototype属性:属于构造函数,指向原型对象

无论何时何地,只要创建了一个函数,那么该函数就会获得一个prototype属性,这个prototype属性是一个指针,指向该函数的 原型对象

(3)原型对象

原型对象的用途是它包含了可以由 特定类型的所有实例(即由同一个构造函数创建的实例)共享的属性和方法

(4)constructor属性:属于原型对象,指向构造函数

默认情况下,原型对象都会自动获得一个constructor属性,这个constructor属性是一个指针,指向构造函数。

(5)实例

实例是通过构造函数通过new操作符创建的对象。

(6)proto:属于实例,指向原型对象

通过构造函数创建的实例会包含一个__proto__属性,这个__proto__属性是一个指针,指向构造函数的 原型对象

NOTE: 这个属性其实被称为[[Prototype]],在js中无标准方式访问。在Firefox、Safari、Chrome中js可以通过__proto__属性访问,在其它地方没有办法访问。

构造函数、原型对象、实例的关系可以用下图表示:

具体到上Eg1,可以用下图表示:

3.原型模式相关规律

(1)实例属性会屏蔽原型属性

如果在实例中添加了一个属性,该属性与原型中的一个属性同名,那么就会在该实例中创建该属性,该属性会屏蔽原型中的同名属性,但 不会修改原型中的同名属性,即使将该属性设为null

使用delete操作符删除实例中的属性后,实例就恢复了对原型中同名属性的连接

Eg2:

function Person() {
}

Person.prototype.name = "Bonnie";
Person.prototype.age = 26;
Person.prototype.sayName = function() {
  console.log(this.name)
}

var person1 = new Person();
var person2 = new Person();

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

(2) 重写整个原型对象,原型对象会丧失constructor属性的指向

Eg3:

function Person() {
}

Person.prototype = {
  name:'Bonnie',
  age:26,
  sayName:function() {
    console.log(this.name);
  }
}

var person3 = new Person();
person3.constructor === Person;//false NOTE:实例会继承原型对象上的constructor方法。

如下重新调整constructor:

Person.prototype.constructor = Person;
person3.constructor === Person;//true

//但是这样会使constructor属性变为可枚举属性
Object.getOwnPropertyNames(Person.prototype);//["name", "age", "sayName", "constructor"]
//Object.getOwnPropertyNames包含不可枚举属性
Object.keys(Person.prototype);// ["name", "age", "sayName", "constructor"]
//Object.keys不包含不可枚举属性

更好的办法是这样调整constructor:

Object.defineProperty(Person.prototype, 'constructor',{
  enumerable: false,
  value: Person
});
person3.constructor === Person;//true

Object.getOwnPropertyNames(Person.prototype);//["name", "age", "sayName", "constructor"]
Object.keys(Person.prototype);//["name", "age", "sayName"]

(3)原型的动态性

对原型对象在任何时候所做的任何修改都能立即从实例中反映出来。

先创建实例再修改原型:

Eg4:

var person4 = new Person();
person4.sayHello();//Uncaught TypeError:person4.sayHello is not a function
Person.prototype.sayHello = function() {
  console.log('hello!');
}
person4.sayHello();//hello

但是 如果对原型对象进行整个重写,那么就切断了实例与原型对象的联系,即 实例的__proto__指针不再指向原型对象

Eg5:

var person5 = new Person();
person5.sayHi();//Uncaught TypeError:person4.sayHi is not a function

Person.prototype = {
  name:'Bonnie',
  age:26,
  sayName: function() {
    console.log(this.name)
  },
  sayHi: function() {
    console.log('hi~');
  }
}

person5.sayHi();//Uncaught TypeError:person4.sayHi is not a function

(4)原型模式具有共享本性,不但共享方法,也会共享属性(不合理)

如果共享的是方法,那么非常合适;

如果共享的是基本值,那么通过在实例上添加同名属性,可以覆盖原型中的同名属性;

但是如果共享的是引用类型值(如数组),那么如果在一个实例上修改,也会影响到其他属性。

例如共享的是数组:

Eg6:

  function Person() {
  }

  Person.prototype.name = "Bonnie";
  Person.prototype.age = 26;
  Person.prototype.sayName = function() {
    console.log(this.name)
  }
  Person.prototype.friends = ['Summer','Huiyun'];

  var person1 = new Person();
  var person2 = new Person();

  person1.friends.push('Angela');//如果直接push修改,会影响其他实例
  console.log(person1.friends);//['Summer','Huiyun','Angela'];
  console.log(person2.friends);//['Summer','Huiyun','Angela'];

  person1.friends = ['Angela'];//如果直接赋值覆盖,则不会影响其他实例
  console.log(person1.friends);//['Angela'];
  console.log(person2.friends);//['Summer','Huiyun','Angela'];

为了解决这个问题,将构造函数模式和原型模式组合起来使用将是个好办法。 组合使用构造函数模式和原型模式:

Eg6:

function Person(name,age) {
  this.name = name;
  this.age = age;
  this.friends = ['Tom', 'Lulu'];
}
Person.prototype.sayName = function () {
  console.log(this.name);
}

var person6 = new Person('Kate', 18);
var person7 = new Person('Bob', 28);
console.log(person6.friends);//["Tom", "Lulu"]
console.log(person7.friends);//["Tom", "Lulu"]
person6.friends.push('Linda');
console.log(person6.friends);// ["Tom", "Lulu", "Linda"]
console.log(person7.friends);//["Tom", "Lulu"]

这样每个实例都有一份自己的属性副本,又共享着对方法的引用。

4. 原型模式相关方法

(1)原型对象.isPrototypeOf(实例)

验证某原型对象是否为某实例的原型对象。

Person.prototype.isPrototypeOf(person1);// true

(2)Object.getPrototypeOf(实例)

获取实例的原型对象。

Object.getPrototypeOf(person1) === Person.prototype;//true

(3)实例.hasOwnProperty(属性)

检测一个属性是否存在于实例本身中,还是存在于原型中。

person1.hasOwnProperty('name');//true
person2.hasOwnProperty('name');//false

(4) in

通过对象能够访问给定的属性,则返回true,无论该属性是在实例还是在原型中, 无论该属性是否可枚举

'name' in person1;//true
'name' in person2;//true

(5) for...in

for...in循环返回对象所有的 可枚举 属性,无论该属性是在实例还是在原型中

for (let prop in person1) {
  console.log(`${prop}: ${person1[prop]}`);
} 
/** 输出:
name: Summer
age: 26
sayName: function() {
  console.log(this.name)
}
*/

(6) Object.keys(对象)

取得对象上所有的 可枚举实例 属性组成的数组

Object.keys(person1);//["name"]
Object.keys(person2);//[]

(7) Object.getOwnPropertyNames(对象)

获取对象的所有 实例 属性,无论该属性是否可枚举

方法(3)~(7)特点对比总结

方法 是否包含不可枚举属性 是否查找prototype链
hasOwnProperty ×
in
for...in ×
Object.keys() × ×
Object.getOwnPropertyNames() ×

二、实现继承的主要范式:原型链

ECMAScript将 原型链 作为实现继承的主要方法。

原型链的基本思想就是 让一个引用类型继承另一个引用类型的属性和方法。参考构造函数、原型对象、实例直接的关系,原型链的形成就是让 一个类型的实例 作为 另一个类型原型对象

1.原型链基本实现

function SuperType() {
  this.superProperty = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); //将 SuperType类型的实例 作为 SubType类型的 原型对象, 这样就重写了SubType的原型对象(没有使用SubType默认的原型对象), 实现了继承。

SubType.prototype.getSubValue = function() {
  return this.subProperty;
}

const subTypeInstance = new SubType();
console.log(subTypeInstance.getSuperValue());//true

这样SubType的实例(即subTypeInstance)的__proto__指向SuperType的实例(即SubType的原型),SuperType的实例的__proto__又指向SuperType的原型。

NOTE:

  • 这时候subTypeInstance的constructor指向的是SuperType而非SubType,因为SubType的prototype被重写为SuperType的实例。

2. 实现原型链的相关注意点

(1) 所有函数的默认原型对象都是Object类型的实例

所有函数的默认原型都是Object类型的实例,故 SuperType的原型对象(即SuperType.prototype)就是Object类型的实例,其__proto__指向的Object类型的原型对象。比较绕

上例可以总结为,SubType继承了SuperType,SuperType继承了Object。

(2) instanceof和isPrototypeOf() 测试实例的原型链

instanceof可以测试某个实例的类型和其继承的类型。

subTypeInstance instanceof SubType;//true
subTypeInstance instanceof SuperType;//true
subTypeInstance instanceof Object;//true

isPrototypeOf()也可以测试某个实例的原型链中出现过的原型对象

SubType.prototype.isPrototypeOf(subTypeInstance);//true
SuperType.prototype.isPrototypeOf(subTypeInstance);//true
Object.prototype.isPrototypeOf(subTypeInstance);//true

(3)子类型给继承的原型对象添加/重写方法 必须在 子类型的原型对象已经替换为新的原型对象 之后


function SuperType() {
  this.superProperty = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); //*1 替换SupType的默认原型对象为 SuperType的实例,实现继承

SubType.prototype.getSubValue = function() {
  return this.subProperty;
}

SubType.prototype.getSuperValue = function() { //*2 在原型对象替换为新的之后,再给继承的原型对象添加/重写方法
  return 'newSuperValue';
}

var subTypeInstance = new SubType();
var superTypeInstance = new SuperType();
subTypeInstance.getSuperValue();//'newSuperValue' 重写原型对象中已经存在的方法后会屏蔽原型对象的旧方法
superTypeInstance.getSuperValue();//true 重写原型对象中已经存在的方法后,对于原型对象的类型本身的实例没有影响

如果将*2 放在 *1之前,则重写不能生效:

function SuperType() {
  this.superProperty = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype.getSuperValue = function() { //在原型对象替换为新的之后,再给继承的原型对象添加/重写方法
  return 'newSuperValue';
}

SubType.prototype = new SuperType(); //替换SupType的默认原型对象为 SuperType的实例,实现继承

SubType.prototype.getSubValue = function() {
  return this.subProperty;
}



var subTypeInstance = new SubType();
var superTypeInstance = new SuperType();
subTypeInstance.getSuperValue();//true
superTypeInstance.getSuperValue();//true

(4)子类型实例对继承的原型对象添加/重写方法,不能使用对象字面量的方式

如果使用对象字面量的方式重写,则会重写整个原型链,之前所做的对原型对象的替换就是无效的。

function SuperType() {
  this.superProperty = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); //*1 替换SupType的默认原型对象为 SuperType的实例,实现继承

SubType.prototype = { //对象字面量的方式重写,是的*1行无效
  getSubValue: function() {
    return this.subProperty;
  },
  getSuperValue: function() {
    return 'newSuperValue'
  }
}

var subTypeInstance = new SubType();
subTypeInstance.getSuperValue();//'newSuperValue' 

console.log(subTypeInstance instanceof SuperType);//false

(5)子类型实例对继承的原型对象修改引用类型值,会影响其他所有子类型实例的属性,但是不会影响原型链上其他被继承类型的实例的属性


function SuperType() {
  this.superProperty = true;
  this.colors = ['red', 'yellow'];
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); 

var subTypeInstance = new SubType();
var subTypeInstance2 = new SubType();
var superTypeInstance = new SuperType();

subTypeInstance.colors.push('green');//*1
console.log(subTypeInstance.colors);//["red", "yellow", "green"]
console.log(subTypeInstance2.colors);//["red", "yellow", "green"]
console.log(superTypeInstance.colors);//["red", "yellow"]

交换*1和 *2 行的位置,结果依然一样:


function SuperType() {
  this.superProperty = true;
  this.colors = ['red', 'yellow'];
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); 

var subTypeInstance = new SubType();



subTypeInstance.colors.push('green');//*2

var subTypeInstance2 = new SubType();
var superTypeInstance = new SuperType();//*1

console.log(subTypeInstance.colors);//["red", "yellow", "green"]
console.log(subTypeInstance2.colors);//["red", "yellow", "green"]
console.log(superTypeInstance.colors);//["red", "yellow"]

参考资料

《JavaScirpt高级程序设计》6.2.3,6.3.1

《你不知道的JavaScript(上)》Chapter5

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。

暂无文章

浅谈对python pandas中 inplace 参数的理解

这篇文章主要介绍了对python pandas中 inplace 参数的理解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 pandas 中 inplace 参数在很多函数中都会有,它的作用是:是否...

Linux就该这么学
27分钟前
13
0
C++ 从基本数据类型说起

前言 int 在32位和64位操作系统,都是四个字节长度。为了能编写一个在32位和64位操作系统都能稳定运行的程序,建议采用std::int32_t 或者std::int64_t指定数据类型。*与long随操作系统子长变...

osc_sxdofc9c
27分钟前
9
0
游戏音乐的作用以及起源

游戏音乐是由特殊的音乐、语言符号、美学符号组成,在电子游戏的发展下,游戏音乐越来越成熟,游戏音乐与美术相融合,能够带给玩家视觉与声音的感官冲击,形成游戏音乐所具有的独特的审美效果...

奇亿音乐
27分钟前
10
0
2020,最新Model的设计-APP重构之路

很多的app使用MVC设计模式来将“用户交互”与“数据和逻辑”分开,而model其中一个重要作用就是持久化。下文中设计的Model可能不是一个完美的,扩展性强的model范例,但在我需要重构的app中,...

osc_mfzkzkxi
28分钟前
4
0
面对职业瓶颈,iOS 开发人员应该如何突破?

我们经常看到 iOS 开发人员(各种能力水平都有)的一些问题,咨询有关专业和财务发展方面的建议。 这些问题有一个共同点:前面都会说“我现在遇到了职业困境”,然后会问一些诸如“我是否应该...

osc_gfpedeca
29分钟前
21
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部