JavaScript学习系列之深入原型链与继承的实现
2017-02-22 14:21
429 查看
JavaScript是一种解释型语言,它不需要编译成二进制文件,是一种动态语言。不同于Java中继承的实现方式,JavaScript实现继承主要依靠强大的原型链机制来实现。
如果obj对象中包含名为a的属性,那么它会屏蔽所有原型链上的名为a的属性。这条赋值语句只会修改已有的属性。
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性且没有被标记为只读,那就会直接在obj中添加一个名为a的新属性。
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性且被标记为只读,那么无法修改已有属性,也无法直接在obj中添加一个名为a的新属性。代码运行在严格模式下会报错。
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性并且它是一个setter,那就会调用这个setter,a不会被添加到obj中。
有些情况下会产生隐式屏蔽。
因为obj.a++相当于obj.a = obj.a + 1,也是一个赋值语句。
上述代码中最重要的一句就是:
Bird.prototype = Object.create(Animal.prototype);。Object.create()方法会创建一个新的对象并把对象内部的proto属性(原型链)关联到指定的对象(本例中是Animal.prototype)。
注意:最好不要把这句话换成下面两种形式
Bird.prototype = Animal.prototype;
这种形式并不会创建一个新对象,只是让Bird.prototype指向Animal.prototype,它们其实是一个对象。当我们为子类Bird的原型添加新的属性时,这个属性也相当于添加到Animal的原型中。这就导致其他类继承于Animal时,有了多余的属性。
Bird.prototype = new Animal();
这种形式的确可以创建一个关联到Animal.prototype的新对象。但是它有许多副作用,比如给Bird的原型添加多余的属性,或者可能做了一些不必要的操作,甚至可能影响Bird的“后代”。
如果你向目标对象中显式混入超过一个对象,就可以模仿多重继承行为。但是仍没有直接的方式处理函数和属性的同名问题。有些库提出“晚绑定”技术,但是这些技术可能会降低性能得不偿失。
一 原型链
访问一个对象的属性时,先在基本属性中查找,如果没有,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 Object.prototype)仍然没有找到属性,就会返回undefined。var obj = {}; obj.a //输出undefined var another = { a : 2}; obj.__proto__ = another; obj.a //输出2
对象属性屏蔽问题
当读取一个对象obj的属性a时候,如果obj本身不存在属性名为a的属性,就去原型链中查找属性。但是在obj中写入一个属性a的时候,即执行赋值语句obj.a = 4时候,则分如下几种情况:如果obj对象中包含名为a的属性,那么它会屏蔽所有原型链上的名为a的属性。这条赋值语句只会修改已有的属性。
var another = { a : 2}; var obj = {a : 4}; obj.__proto__ = another; obj.a //输出4 obj.a = 5; another.a //输出2 obj.a //输出5
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性且没有被标记为只读,那就会直接在obj中添加一个名为a的新属性。
var another = { a : 2}; var obj = {}; obj.__proto__ = another; obj.a //输出2 obj.hasOwnProperty('a')//输出false obj.a = 5; another.a //输出2 obj.a //输出5 obj.hasOwnProperty('a')//输出true
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性且被标记为只读,那么无法修改已有属性,也无法直接在obj中添加一个名为a的新属性。代码运行在严格模式下会报错。
如果obj对象中不包含名为a的属性,其原型链上包含名为a的属性并且它是一个setter,那就会调用这个setter,a不会被添加到obj中。
有些情况下会产生隐式屏蔽。
var another = { a : 2}; var obj = {}; obj.__proto__ = another; another.a//输出2 obj.a//输出2 obj.hasOwnProperty('a')//输出false obj.a++; another.a//输出2 obj.a//输出3 obj.hasOwnProperty('a')//输出true
因为obj.a++相当于obj.a = obj.a + 1,也是一个赋值语句。
二 原型链继承与类式继承结合(借用构造函数)
需要注意的一点是原型继承中的原型是指函数的原型,即每个函数都有一个prototype属性与之对应。这需要与原型链中的proto属性区分开来。原型继承最重要的就是将父类的原型继承到子类上来。//父类Animal function Animal(name) { this.name = name || 'Animal'; } //Animal公有方法 Animal.prototype.breath = function() { console.log(this.name + ' is breathing!'); }; // 子类Bird function Bird(name) { //通过借用构造函数来实现对实例属性的继承 Animal.call(this, name); } //使用原型链实现对原型属性和方法的继承 Bird.prototype = Object.create(Animal.prototype); //Bird公有方法 Bird.prototype.fly = function(){ console.log(this.name + ' is flying!'); } //实例 var bird = new Bird('birdman'); bird.breath();//birdman is breathing! bird.fly();//birdman is flying!
上述代码中最重要的一句就是:
Bird.prototype = Object.create(Animal.prototype);。Object.create()方法会创建一个新的对象并把对象内部的proto属性(原型链)关联到指定的对象(本例中是Animal.prototype)。
注意:最好不要把这句话换成下面两种形式
Bird.prototype = Animal.prototype;
这种形式并不会创建一个新对象,只是让Bird.prototype指向Animal.prototype,它们其实是一个对象。当我们为子类Bird的原型添加新的属性时,这个属性也相当于添加到Animal的原型中。这就导致其他类继承于Animal时,有了多余的属性。
Bird.prototype = new Animal();
这种形式的确可以创建一个关联到Animal.prototype的新对象。但是它有许多副作用,比如给Bird的原型添加多余的属性,或者可能做了一些不必要的操作,甚至可能影响Bird的“后代”。
三 模拟多重继承的混入机制
JavaScript本身不提供多重继承的功能,使用多重继承的代价也太高。但是我们可以模拟实现多重继承,也就是混入机制。在jQuery中把混入叫做extend,我们一般把它叫做mixin。下面看非常简单的mixin函数。1.显式混入
function mixin(sourceObj, targetObj){ for(var key in sourceObj){ if(!(key in targetObj)){ targetObj[key] = sourceObj[key]; } } return targetObj; } var Animal = { name : 'Animal', breath : function() { console.log(this.name + ' is breathing!'); } }; //创建一个空对象把Animal的内容复制进去 var Bird = mixin(Animal, {}); //把新内容复制到bird中 mixin({ name : 'birdman', fly : function(){ console.log(this.name + ' is flying!'); } }, Bird);
如果你向目标对象中显式混入超过一个对象,就可以模仿多重继承行为。但是仍没有直接的方式处理函数和属性的同名问题。有些库提出“晚绑定”技术,但是这些技术可能会降低性能得不偿失。
2.寄生继承
显式混入的一种变体称为“寄生继承”。//父类Animal function Animal(name) { this.name = name || 'Animal'; } Animal.prototype.breath = function() { console.log(this.name + ' is breathing!'); }; // 寄生类Bird function Bird(name) { var bird = new Animal(); bird.name = name || 'bird'; bird.fly = function(){ console.log(this.name + ' is flying!'); }; return bird; } //实例 var bird = new Bird('birdman'); bird.breath();//birdman is breathing! bird.fly();//birdman is flying!
3.隐式混入
var Animal = { name : 'Animal', breath : function() { console.log(this.name + ' is breathing!'); } }; var Bird ={ name : 'birdman', breath : function() { //隐式把breath混入Bird中 Animal.breath.call(this); } };
相关文章推荐
- javascript类学习(一)——构造器与原型链实现简单的继承
- 深入Java集合学习系列:HashSet的实现原理
- javascript继承学习系列之三:对象伪装(Object Masquerading)
- 深入理解JavaScript系列(6) 强大的原型和原型链
- 深入Java集合学习系列:HashSet的实现原理
- 深入理解JavaScript系列(18):面向对象编程之ECMAScript实现(推荐)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:LinkedHashSet的实现原理
- 深入理解JavaScript系列(6) 强大的原型和原型链
- 深入理解JavaScript系列(5):强大的原型和原型链
- 深入Java集合学习系列:HashMap的实现原理
- 深入Java集合学习系列:ArrayList的实现原理
- AJAX之旅(2):javascript中类的深入研究-实现和继承
- 深入理解JavaScript系列(18):面向对象编程之ECMAScript实现(推荐)
- AJAX之旅(2):javascript中类的深入研究-实现和继承
- javascript继承学习系列之一:初识JS的OOP
- 深入Java集合学习系列:HashMap的实现原理
- 深入Java集合学习系列:ArrayList的实现原理
- 深入Java集合学习系列:HashMap的实现原理
- 深入理解JavaScript系列(5):强大的原型和原型链