重学原型链

一切源于一个简单的案例:

function Foo(){
    this.name = name;   // 原始属性or方法
}
Foo.prototype.say = function(){   // 原型属性or方法
    console.log('hello')
}
var f1 = new Foo();
f1.say();    //hello
Foo.prototype = {};
var f2 = new Foo();
f1.say();   // hello
f2.say();  //报错

本身是一个很简单的关于原型的例子,可我突然就不明白(也许我中了邪)了为什么f1仍能访问say方法。于是乎怀着学习的态度重学了原型链相关的内容。

写在前面

为了方便描述 我们把定义在方法体内的属性和方法称之为原始属性和方法。把定义在原型上的属性和方法称之为原型属性和方法

原型(原型对象)与原型链

提起原型不得不把原型的‘家谱’摆上,请看图:
在这里插入图片描述
把家谱看的明白才能了解家族的历史。所谓原型就是每个实例对象(object)都有一个__proto__属性,该属性指向构造函数的prototype属性,这个prototype对象也叫做原型对象。举个栗子:

function Person(){};
var p = new Person();
p.__proto__ === Person.prototype   //true

其实Person本身也是个对象(Function的一个实例),所以Person.__proto__又指向了Object.prototype,而Object.prototype指向null(到头了),这就形成了一个链条也就是所谓的原型链。就像是这样
在这里插入图片描述

new操作符

使用new操作符实例化一个对象是很常见的操作,那么new做了哪些事情呢,我们先写一个例子:

function Test(name){
    this.name = name;
}
Test.prototype.print = function(){
    console.log(this.name);
}
var t = new Test('tom');
  1. 创建一个空对象 var t = {}
  2. 把第一步创建的空对象作为上下文执行构造函数,类似这样 Test.call(t)
  3. 把t.proto 指向 Test.prototype(获取原型对象上的属性和方法)
  4. 返回对象t

这里有两个地方需要注意
1. 上述第三步中‘指向’的意思是相当于把当前 Test.prototype 的地址复制给实例对象t,后续如果修改了 Test.prototype的指向,也不会影响修改前创建的实例。这也就是为什么本文最开始提到的例子中修改Foo.prototype = {}后 f1仍能调用say方法的原因。画个图:
在这里插入图片描述
2. 如果构造函数有返回值且返回的是对象类型(如 object,date,array)则创建的实例为构造函数的返回值。例如这样

function Test(name){
    this.name = name
    return {age:18}
}
var t = new Test('tom')
t ==> {age: 18} 而不是 {name: 'tom'}
继承

如果试图引用对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(constructor.prototype)里去找这个属性.

提到原型链就不得不提js里的继承

原型链继承
function Person(name,age){}
Person.prototype.introduce = function(){
    console.log('hello world');
}
Person.prototype.money = [100,200,300];

function Tom(address){}
Tom.prototype = new Person();
Tom.prototype.constructor = Tom;
var tom = new Tom();
tom.introduce();  //  hello world
tom.money        // [100,200,300]

通过原型链继承tom实例可以访问到Person的原型属性和方法,但是这种方法有个致命的缺点是所有的实例对象都可以访问且修改Person原型上的属性,而原型上的属性是引用类型时实例间的操作就会相互影响,实际开发中并不希望这样的事情发生,接上面的例子:

tom.money[0] = 0;
var tom2 = new Tom();
tom2.money  // [0,200,300]

新初始化的tom2的money属性变成了tom修改后的值。

构造函数继承

为了解决原型链继承的弊端,我们可以使用构造函数继承。

function Person(){
    this.money = [100,200,300]
    this.say = function(){ 
        console.log('hello world'); 
    }
}
Person.prototype.run = function(){
    console.log('run');
}

function Tom(){
    Person.call(this)  //构造函数继承
}

var tom = new Tom();
var tom2 = new Tom();
tom.money[0] = 0;
tom2.money  // [100,200,300]
tom2.run() // 报错

这种方法解决了原型链继承的缺点,可是这种方法不能继承原型属性和方法.造成内存上的浪费。

组合继承
function Person(){
    this.money = [100,200,300]
}
Person.prototype.run = function(){
    console.log('run');
}
function Tom(){
    Person.call(this)  //构造函数继承
}
Tom.prototype = new Person();  //原型链继承
Tom.prototype.constructor = Tom;

这种方法虽然父类构造函数被调用两次(实例对象中存在两份父类的属性),但是他融合了原型链继承和构造函数继承的优点。点赞!

寄生继承
function Person(){
    this.money = [100,200,300];
}
Person.prototype.run = function(){
    console.log('run');
}

function Tom(){
    Person.call(this);  // 通过构造函数继承实现继承父类的原始属性和方法
}
Tom.prototype = Object.create(Person.prototype)  
// 该方法会使用指定的原型对象去创建一个新的对象来实现继承父父类的原型方法和属性

Tom.prototype.constructor = Tom;

这种方法解决了组合继承内存浪费的缺点,大大的赞!!

以上就是这一次脑袋中邪后重学原型链的相关,特发此博文以示纪念!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值