Javascript学习笔记-原型链

Javascript原型链.png

对于Javascript原型链,是Javascript中很重要的内容,要理解关键有三点:
Javascript中原型链作用是为了实现Javascript中的继承机制。
Javascript中原型链是利用对象关联的方式实现的(不同于一般的类复制)。
Javascript中之所以能使用原型链来实现继承,关键是Javascript对象中属性检索机制
于是从类基础开始说起。

1. 面向对象

1.1 类基础

在面向对象语言中,经常会使用到类。类是一种设计模式,是某种事物的描述,类一般存在构造函数,构造函数是用于构建实例,实例是类的具体实现,类和实例的关系就类似于蓝图和建筑物的关系一样。

1.2 继承和多态

继承和多态是面向对象的两个重要的特性。对于类似Java的面向对象语言来说,继承是通过创建实例化的过程中,复制父类的属性和方法来实现,并且通过重写父类的方法(实现了多态)。

2. Javascript中对象属性

这里先提前引入原型链,主要是为了描述,具体原型链继承的方式在 3.2 原型链继承 中描述。

2.1 属性获取

Javascript的对象中属性获取的时候,会根据一定的步骤进行取值:
首先判断对象中是否存在该属性,如果存在该属性则返回属性值。
否则访问该对象的原型对象(__proto__),判断其是否存在该属性,如果存在,则返回该属性值。
否则继续遍历原型对象,直到Object.prototype如果仍然没有找到该属性则返回undefined

var o1 = {a: 1};
console.log(o1.a); // 1, obj中存在a,直接返回
function F(){};
F.prototype.b = 2;
var o2 = new F;
console.log(o2.hasOwnProperty('b')); // false
console.log(o2.b); // 2, obj的原型对象中存在属性b,返回该属性
var o3 = {};
console.log(o3.b); // undefined , 由于obj.__proto__中并不存在该属性,所以返回undefined

2.2 属性设置

属性设置也会遍历原型链,但是根据属性存在位置以及原型链上属性描述符的不同,可能会存在不同的设置结果:

// 1. 对象中存在该属性 , 直接修改属性值
var o1 = {a: 1};
console.log(o1.__proto__.a); // undefined
console.log(o1.a); // 1
o1.a = 2; 
console.log(o1.__proto__.a); // undefined ,原型链上并没有增加该属性
console.log(o1.a); // 2,当前对象属性值被修改

// 2. 对象中不存在该属性,原型链上也不存在该属性,则对象中增加该属性
var o2 = {};
console.log(o2.__proto__.a); // undefined
console.log(o2.hasOwnProperty('a')); // false
o2.a = 1;
console.log(o2.__proto__.a); // undefined ,原型链上并没有增加该属性
console.log(o2.hasOwnProperty('a')); // true

// 3. 对象中不存在该属性,原型链上存在该属性且不为只读,则当前对象增加该属性,并屏蔽原型链属性值
function F() {}
F.prototype.a = 1;
var o3 = new F;
console.log(o3.__proto__.a); // 1
console.log(o3.hasOwnProperty('a')); // false
o3.a = 2;
console.log(o3.__proto__.a); // 1, 原型链上属性没有发生变化
console.log(o3.hasOwnProperty('a')); // true,对象增加属性
console.log(o3.a); // 2,屏蔽原型链上属性

// 4. 对象中不存在该属性,原型链上该属性为只读,则不会在当前对象中增加该属性,且该属性值不变
function F() {}
Object.defineProperty(F.prototype, 'a', {
    writable: false,
    value: 1
})
var o4 = new F;
console.log(o4.__proto__.a); // 1
console.log(o4.hasOwnProperty('a')); // false
o4.a = 2;
console.log(o4.__proto__.a); // 1, 原型链上属性没有发生变化
console.log(o4.hasOwnProperty('a')); // false,对象中没有增加该属性值
console.log(o4.a); // 1,获取原型链上属性

// 5. 对象中不存在该属性,原型链上存在该属性的setter方法,则会调用该setter方法
function F() {}
Object.defineProperty(F.prototype, 'a', {
    set(){
        console.log('set a');
    }
})
var o5 = new F;
console.log(o5.__proto__.a); // undefined
console.log(o5.hasOwnProperty('a')); // false
o5.a = 2; // 'set a'
console.log(o5.__proto__.a); // undefined, 原型链上属性没有发生变化
console.log(o5.hasOwnProperty('a')); // false,对象中没有增加该属性值
console.log(o5.a); // unedefined,获取原型链上属性值并没有变

3. 继承

3.1 混入

1.2 继承和多态 中所说,一般面向对象编程语言的继承都是在对象实例化的时候,采用复制的方式将父类的内容深度复制一份到实例中,由于Javascript中并没有实例化的过程,但是可以通过对象复制的方式来实现继承关系,这样的方式可以叫做混入。
一般的显示混入,在混合对象的过程中,会将目标对象中不存在的属性进行复制添加。

  // 显示混入函数
  function mixin(target, source) {
    for(let key in source){
      if(!target.hasOwnProperty(key)){  // 不存在该属性,则添加该属性
        target[key] = source[key];
      }
    }
  }

这样就可以将源对象的属性添加到目标对象属性中,类似复制的原理实现继承关系
当然也可以直接创建一个对象包含所有属性,用新对象属性覆盖掉原对象属性并返回。

3.2 原型继承

3.2.1 Function.prototypeObject.__proto__

Javascript中默认的继承机制并没有使用类似上面的复制机制实现,而是利用Javascript中的对象,通过对象关联的方式进行实现继承,也就是原型继承。
首先Javascript的Function对象中,默认包含一个不可枚举的prototype属性,该属性的值为对应的原型对象,其结果为包含一个不可枚举属性constructor的对象

function F() {}
console.log(F.hasOwnProperty('prototype')); // true
console.log(Object.propertyIsEnumerable(F.prototype)); // false
console.log(F.prototoype); // { constructor: f}

注意:这里我们可以使用new F的方式创建对象,这种方式类似Java等面向对象语言中的实例化,F类似构造函数,但是Javascript中不存在类,所以这只能理解为构造函数方法调用。且这里的constructor并不代表对象的构造关系。
Javascript对象中存在__proto__属性,指向对象的原型对象

function F() {};
var f = new F();
console.log(f.__proto__ === F.prototype); // true

3.2.2 Javascript原型继承实现

原型继承利用了Object.create()方法实现

function F() {}
F.prototype.a = 1;
function G() {}
G.prototype = Object.create(F.prototype);
var g = new G;
console.log(g.a); // 1,根据原型链机制获取到原型对象F.protoype中属性'a'的值
console.log(g.__proto__ === G.prototype); // true
console.log(g.__proto__.__proto__ === F.prototype); // true

其中,Object.create()的实现原理:

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

通过代码我们可以看到,利用Object.create()关联了对象,使得GF联系了起来,同样通过这个就可以知道为什么Object.create(null)创建出来的对象对象不在Object.prototype链上了

3.3 行为委托

对于Function的对象,利用Function.prototype原型链,创建了对象的关联,对于两个对象之间,根据Object.create()的原理,也可以直接创建关联,通过这样的方法,在属性获取不到的时候,当前对象会委托关联对象进行数据获取。

var o1 = {a: 1};
var o2 = Object.create(o1);
console.log(o2.a); //1 , 获取o1对象上的值

因为Javascript中继承本身就是对象之间的关联,所以比起使用原型继承的方式,需要使用new等看起来像构造函数的方式实现继承关系,利用行为委托更优秀。Javascript继承实现的本质对象关联

// 原型链实现继承,通常子类含有父类相同方法并进行重写
function F(width, height){
  this.width = width;
  this.height = height;
}
F.prototype.width = 1
F.prototype.height = 1;
F.prototype.square = function () {
  return this.width * this.height
}
function G(width, height){
    F.call(this, width, height); // 需要使用这种显示的方式来调用父类构造器实现初始化
}
G.prototype = Object.create(F.prototype);
G.prototype.square = function () {
  return 1/2 * this.width * this.height
}
var g = new G(2, 1);
g.square(); // 1;

// 行为委托实现继承,子对象和父对象方法名一般不同
var F = {
  init(width, height) {
    this.width = width;
    this.height = height;
  },
  rectSquare() {
    return this.width * this.height
  }
}
var G = Object.create(F);
G.build = function(width, height){
  this.init(width, height); // 利用this来实例化对象
};
G.angelSquare = function() {
  return 1/2 * this.width * this.height;
}
G.build(2, 1);
G.angelSquare(); // 1;

4. 其他

4.1 ES6中class

ES6中引入了class关键字和extends关键字来实现Javascript中的继承,使得看起来更像一般的面向对象语言,但是实际上这里的class只是原型继承的语法糖,本质还是对象的关联,并非类的复制,所以当改变原型对象的内容会影响到对应的对象。

class F {
  constructor(name) {
    this.name = name;
  }
  log() {
    return 'log:' + this.a
  }
}
var f = new F('patrick');
F.prototype.log = function () {
  return 'new log:' + this.name
}
console.log(f.log()); // new log: patrick 修改了原型对象,影响了实际对象。

4.2 hasOwnProperty

由于我们所说的Javascript对象中属性获取和设置是需要在原型链上进行查找的,所以使用hasOwnProperty值来判断是否为当前对象属性,可以阻断原型链上的查找,急速性能

4.3 Object.prototype

对于所有的对象,最终原型对象都指向Object.prototype,而且Object.prototype的原型对象为null

5. 参考:

《你不知道的Javascript(上篇)》
MDN Inheritance and the prototype chain

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容