首页 > 设计 > WEB开发 > 正文

JavaScript 原型继承

2019-11-02 18:23:45
字体:
来源:转载
供稿:网友

简介

继承是面向对象语言的一个重要的特性和概念。许多的面向对象语言中都支持两种继承方式:接口继承和实现继承。

javaScript 仅支持实现继承,主要是靠原型链来实现。

原型: Javascript 的所有函授都有一个PRototype属性,这个属性引用了一个对象,即原型对象,也简称原型。 注:在JavaScript中对象可以分为函数对象普通对象。只用函数对象包含prototype属性。凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。

原型链:由于原型对象本身也是对象,它也有自己的原型。而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个就是原型链。 JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找。如果找到,直接返回值;如果整个链都遍历且没有找到属性,则返回undefined。原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。

构造函数:当任意一个普通函数用于创建一类对象时,它就被称作构造函数,或构造器。构造函数必须满子以下几点: 1、在函数内部对新对象(this)的属性进行设置,通常是添加属性和方法。 2、构造函数可以包含返回语句(不推荐),但返回值必须是this,或者其它非对象类型的值。

关系: 1、构造函数通过 new 来创建实例对象;构造函数包含一个原型对象(prototype); 2、原型对象包含一个指向构造函数的指针(constructor); 3、实例对象包含一个指向原型对象的内部指针(_ _ proto _ _); 4、图一: 这里写图片描述 5、图二: 这里写图片描述

通过图二来验证图一: 1、构造函数 A,通过 new 创建了实例对象 B。A.prototype 为 Object{}(原型对象); 2、Object{}(原型对象)的 constructor 指向构造函数 function A(); 3、B. _ _ proto _ _指向Object{}(原型对象)

原型链

图三: 这里写图片描述 注:每个函数都是Function函数创建的对象,所以每个函数也有一个 _ _ proto _ _ 属性指向Function函数的原型。这里需要指出的是,真正形成原型链的是每个对象的 _ _ proto _ _ 属性,而不是函数的prototype属性,这是很重要的。

图四: 这里写图片描述 我们通过图四来验证图三的正确性:

构造函数 A 创建了实例对象 B;B 的原型对象为 Object;Object.prototype 的原型对象指向 null;函数 A 的原型对象为 Function ;Function.prototype 的原型对象为 Object; 完全符合图三。

综图所述

所有的对象都有 _ _ proto _ _ 属性,该属性对应该对象的原型.所有的函数对象都有prototype属性,该属性的值会被赋值给该函数创建的对象的 _ _ proto _ _ 属性.所有的原型对象都有constructor属性,该属性对应创建所有指向该原型的实例的构造函数.函数对象和原型对象通过prototype和constructor属性进行相互关联.

继承

1、基本模式:

var Parent = function() { this.name = 'parent';};Parent.prototype.getName = function() { return this.name;};Parent.prototype.obj = { a: 1};var Child = function() { this.name = 'child';};Child.prototype = new Parent();var parent = new Parent();var child = new Child();console.log(parent.getName()); //parentconsole.log(child.getName()); //child

这种是最简单实现原型继承的方法,直接把父类的对象赋值给子类构造函数的原型,这样子类的对象就可以访问到父类以及父类构造函数的prototype中的属性。

图五:这里写图片描述 如图五可知:child -> Parent -> Function -> Object -> null

注:这种方法的优点很明显,实现十分简单,不需要任何特殊的操作;同时缺点也很明显,如果子类需要做跟父类构造函数中相同的初始化动作,那么就得在子类构造函数中再重复一遍父类中的操作。如果初始化工作不断增加,这种方式是很不方便的。

2、使用构造函数

var Parent = function(name) { this.name = name || 'parent';};Parent.prototype.getName = function() { return this.name;};Parent.prototype.obj = { a: 1};var Child = function(name) { // 在子类构造函数中通过apply调用父类的构造函数来进行相同的初始化工作。 // 这样不管父类中做了多少初始化工作,子类也可以执行同样的初始化工作 Parent.apply(this, arguments);};// Child.prototype = new Parent() ;// 使用这种方式父类构造函数被执行了两次,一次是在子类构造函数中,一次在赋值子类原型时,这是很多余的。// 所以我们还需要做一个改进Child.prototype = Parent.prototype;var parent = new Parent('myParent');var child = new Child('myChild');console.log(parent.getName()); //myParentconsole.log(child.getName()); //myChild

图六:这里写图片描述 根据图六得知,此时的原型链为:child/parent -> Object -> null

注:上面借用构造函数模式最后改进的版本还是存在问题,它把父类的原型直接赋值给子类的原型,这就会造成一个问题,就是如果对子类的原型做了修改,那么这个修改同时也会影响到父类的原型,进而影响父类对象,这个肯定不是大家所希望看到的。

3、临时构造函数模式(圣杯模式)

var Parent = function(name) { this.name = name || 'parent';};Parent.prototype.getName = function() { return this.name;};Parent.prototype.obj = { a: 1};var Child = function(name) { Parent.apply(this, arguments);};var F = function() {};F.prototype = Parent.prototype;Child.prototype = new F();var parent = new Parent('myParent');var child = new Child('myChild');console.log(parent.getName()); //myParentconsole.log(child.getName()); //myChild

图七:这里写图片描述 如上图:此时的原型链为:child -> parent -> Object -> null

4、再次改进

console.log(child.obj.a) ; //1console.log(parent.obj.a) ; //1child.obj.a = 2 ;console.log(child.obj.a) ; //2console.log(parent.obj.a) ; //2

我们在圣杯模式下添加上述代码。查看结果会发现,当我们改变child.obj.a的值的时候,parent对应的也会改变。 出现这个情况是因为当访问child.obj.a的时候,我们会沿着原型链一直找到父类的prototype中,然后找到了obj属性,然后对obj.a进行修改。 改进方式:对父类原型进行拷贝然后再赋值给子类原型,这样当子类修改原型中的属性时就只是修改父类原型的一个拷贝,并不会影响到父类原型。

var deepClone = function(source, target) { source = source || {}; target = target || {}; var toStr = Object.prototype.toString, arrStr = '[object array]'; for (var i in source) { if (source.hasOwnProperty(i)) { var item = source[i]; if (typeof item === 'object') { target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {}; deepClone(item, target[i]); } else { target[i] = item; } } } return target;};var Parent = function(name) { this.name = name || 'parent';};Parent.prototype.getName = function() { return this.name;};Parent.prototype.obj = { a: 1};var Child = function(name) { Parent.apply(this, arguments);};Child.prototype = deepClone(Parent.prototype);var parent = new Parent('myParent');var child = new Child('myChild');console.log(child.obj.a); //1console.log(parent.obj.a); //1child.obj.a = 2;console.log(child.obj.a); //2console.log(parent.obj.a); //1

总结:

说了这么多,其实Javascript中实现继承是十分灵活多样的,并没有一种最好的方法,需要根据不同的需求实现不同方式的继承,最重要的是要理解Javascript中实现继承的原理,也就是原型和原型链的问题,只要理解了这些,自己实现继承就可以游刃有余。

参考来源: 再谈Javascript原型继承:https://segmentfault.com/a/1190000000766541


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表