再看原型链

开场白

输出?

var A= function(){}
    A.prototype.n=1
    var b=new A()
    A.prototype={
        n:2,
        m:3
    }
var c=new A()
console.log(b.n, b.m, c.n, c.m)
console.log(A.prototype.__proto__)

prototype

每个构造函数都有一个prototype属性,指向自己的原型对象;

什么是原型?每个对象创建的时候,都会与之关联另一个对象,这个就是原型对象,会从原型“对象”继承属性和方法。


__proto__

每个实例对象(object)都有一个私有属性__proto__指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象,层层向上直到一个对象的原型对象为null

function Person(name) {
  this.name = name
}

Person.prototype.getName = function () {
  return this.name
}

var person = new Person();
person.name = 'gaoting';
console.log(person.name) // gaoting
console.log(Person.prototype) 
console.log(person.__proto__) 
console.log(person.__proto__ === Person.prototype) // true
WX20180926-161836.png

constructor

每个原型对象,都有一个constructor属性,用来指向自己关联的函数,默认指向关联的构造函数。
constructor 引用同样被委托给了 Person.prototype,而 Person.prototype.constructor 默认指向 Person

3.png

Person.prototype 的 .constructor 属性只是 Person函数在声明时的默认属性。
如果 你创建了一个新对象并替换了函数默认的 .prototype 对象引用,那么新对象并不会自动获 得 .constructor 属性。

function Person(name) {
  this.name = name
}

Person.prototype.getName = function () {
  return this.name
}

var person = new Person();
person.name = 'gao';
console.log(person.constructor === Person, Person.prototype.constructor === Person)
//test constructor
Person.prototype = {
  age: 18,
  company: 'yck'
}

var gao = new Person()
console.log(gao.constructor === Person) // false
// 获取对象原型
console.log(Object.getPrototypeOf(gao)) // { age: 18, company: 'yck' }

gao 并没有 .constructor 属性,所以它会委托 [[Prototype]] 链上的 Person. prototype。但是这个对象也没有 .constructor 属性(不过默认的 Person.prototype 对象有这 个属性!),所以它会继续委托,这次会委托给委托链顶端的 Object.prototype。这个对象 有 .constructor 属性,指向内置的 Object(..) 函数。

以上为构造函数、实例、实例原型之间的关系


基于原型链的继承

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

4.png
function Person(name) {
  this.name = name
}

Person.prototype.getName = function () {
  return this.name
}

function Man (sex) {
  this.sex = sex
}
Man.prototype = new Person()
var man = new Man('male')
man.name = 'qi'
console.log(man.getName())
console.log(man instanceof Man, man instanceof Person) // true true

问题一

即在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做原型指向了另一个对象

问题二

在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType(); // SuperType的原型对象将拥有colors属性,之后的实例对colors的修改,将直接作用到原型上
var instance1 = new SubType(); 
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType(); 
console.log(instance2.colors); //"red,blue,green,black"

错误做法

//和你想要的机制不一样! 
Bar.prototype = Foo.prototype;

只会让Bar.prototype直接引用Foo.prototype对象,当执行类似Bar.prototype.getName的时候,会直接修改Foo.prototype对象本身。这不是想要的结果。

// 基本上满足你的需求,但是可能会产生一些副作用 :( 
Bar.prototype = new Foo();

借用构造函数

为了解决原型中包含引用类型值的问题,可以使用借用构造函数,在子类型构造函数的内部调用超类型构造函数。

function SuperType () {
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.getColors = function(){
    return this.colors.join(',')
}
function SubType () {
  //继承了 SuperType,在新构造函数中调用SuperType初始化对象的代码,每个实例保存colors副本
  SuperType.call(this)
}

var ins = new SubType()
ins.colors.push('black')
var ins2 = new SubType()
console.log(ins2) // ['red', 'blue', 'green']
ins2.getColors() // 报错

缺点,更为致命,无法访问超类型SubType.prototype中定义的函数,仅仅获取了超类型构造函数中定义的属性,没有用到继承的思想。


组合继承

将原型链和组合继承技术组合,通过原型链实现对原型属性和方法的继承,通过借用构造函数,让实例拥有自己的属性。

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

function SubType (name, age = 18) {
  SuperType.call(this, name)
  this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.getAge = function () {
  return this.age
}
var ins = new SubType('seven', 22)
ins.colors.push('black')
var ins2 = new SubType('six', 11)
console.log(ins2)

原型式继承

Object.create()

function Foo(name) { 
    this.name = name;
}
Foo.prototype.myName = function() { 
    return this.name;
};
function Bar(name,label) { 
    Foo.call( this, name ); 
    this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype 

Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.myLabel = function() {
 return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"

参考链接

JavaScript深入之从原型到原型链

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

推荐阅读更多精彩内容