JavaScript程序中实现继承特性的方式总结


Posted in Javascript onJune 24, 2016

概述
JavaScript的所有对象,都有自己的继承链。也就是说,每个对象都继承另一个对象,该对象称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。

原型对象的重要性在于,如果A对象是B对象的原型,那么B对象可以拿到A对象的所有属性和方法。Object.getPrototypof方法用于获取当前对象的原型对象。

var p = Object.getPrototypeOf(obj);

上面代码中,对象p就是对象obj的原型对象。

Object.create方法用于生成一个新的对象,继承指定对象。

var obj = Object.create(p);

上面代码中,新生成的obj对象的原型就是对象p。

非标准的__proto__属性(前后各两个下划线),可以改写某个对象的原型对象。但是,应该尽量少用这个属性,而是用Object.getPrototypeof()和Object.setPrototypeOf(),进行原型对象的读写操作。

var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true

上面代码通过__proto__属性,将p对象设为obj对象的原型。

下面是一个实际的例子。

var a = {x: 1};
var b = {__proto__: a};
b.x // 1

上面代码中,b对象通过__proto__属性,将自己的原型对象设为a对象,因此b对象可以拿到a对象的所有属性和方法。b对象本身并没有x属性,但是JavaScript引擎通过__proto__属性,找到它的原型对象a,然后读取a的x属性。

new命令通过构造函数新建实例对象,实质就是将实例对象的原型绑定构造函数的prototype属性,然后在实例对象上执行构造函数。

var o = new Foo();

// 等同于
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);

原型对象自己的__proto__属性,也可以指向其他对象,从而一级一级地形成“原型链”(prototype chain)。

var a = { x: 1 };
var b = { __proto__: a };
var c = { __proto__: b };

c.x // 1

需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

this的动作指向
不管this在哪里定义,使用的时候,它总是指向当前对象,而不是原型对象。

var o = {
 a: 2,
 m: function(b) {
  return this.a + 1;
 }
};

var p = Object.create(o);
p.a = 12;

p.m() // 13

上面代码中,p对象的m方法来自它的原型对象o。这时,m方法内部的this对象,不指向o,而是指向p。

构造函数的继承
这个小节介绍,如何让一个构造函数,继承另一个构造函数。

假定有一个Shape构造函数。

function Shape() {
 this.x = 0;
 this.y = 0;
}

Shape.prototype.move = function (x, y) {
 this.x += x;
 this.y += y;
 console.info('Shape moved.');
};
Rectangle构造函数继承Shape。

function Rectangle() {
 Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {
 this.base = Shape;
 this.base();
}

// 子类继承父类的方法
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

rect instanceof Rectangle // true
rect instanceof Shape // true

rect.move(1, 1) // 'Shape moved.'

上面代码表示,构造函数的继承分成两部分,一部分是子类调用父类的构造方法,另一部分是子类的原型指向父类的原型。

上面代码中,子类是整体继承父类。有时,只需要单个方法的继承,这时可以采用下面的写法。

ClassB.prototype.print = function() {
 ClassA.prototype.print.call(this);
 // some code
}

上面代码中,子类B的print方法先调用父类A的print方法,再部署自己的代码。这就等于继承了父类A的print方法。

__proto__属性
__proto__属性指向当前对象的原型对象,即构造函数的prototype属性。

var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true

上面代码首先新建了一个对象obj,它的__proto__属性,指向构造函数(Object或obj.constructor)的prototype属性。所以,两者比较以后,返回true。

因此,获取实例对象obj的原型对象,有三种方法。

  • obj.__proto__
  • obj.constructor.prototype
  • Object.getPrototypeOf(obj)

上面三种方法之中,前两种都不是很可靠。最新的ES6标准规定,__proto__属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。

var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p // false

上面代码中,C构造函数的原型对象被改成了p,结果c.constructor.prototype就失真了。所以,在改变原型对象时,一般要同时设置constructor属性。

C.prototype = p;
C.prototype.constructor = C;

c.constructor.prototype === p // true

所以,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。该方法的用法如下。

var o = new Object();

Object.getPrototypeOf(o) === Object.prototype
// true

可以使用Object.getPrototypeOf方法,检查浏览器是否支持__proto__属性,老式浏览器不支持这个属性。

Object.getPrototypeOf({ __proto__: null }) === null

上面代码将一个对象的__proto__属性设为null,然后使用Object.getPrototypeOf方法获取这个对象的原型,判断是否等于null。如果当前环境支持__proto__属性,两者的比较结果应该是true。

有了__proto__属性,就可以很方便得设置实例对象的原型了。假定有三个对象machine、vehicle和car,其中machine是vehicle的原型,vehicle又是car的原型,只要两行代码就可以设置。

vehicle.__proto__ = machine;
car.__proto__ = vehicle;

下面是一个实例,通过__proto__属性与constructor.prototype属性两种方法,分别读取定义在原型对象上的属性。

Array.prototype.p = 'abc';
var a = new Array();

a.__proto__.p // abc
a.constructor.prototype.p // abc

显然,__proto__看上去更简洁一些。

通过构造函数生成实例对象时,实例对象的__proto__属性自动指向构造函数的prototype对象。

var f = function (){};
var a = {};

f.prototype = a;
var o = new f();

o.__proto__ === a
// true

属性的继承
属性分成两种。一种是对象自身的原生属性,另一种是继承自原型的继承属性。

对象的原生属性
对象本身的所有属性,可以用Object.getOwnPropertyNames方法获得。

Object.getOwnPropertyNames(Date)
// ["parse", "arguments", "UTC", "caller", "name", "prototype", "now", "length"]

对象本身的属性之中,有的是可以枚举的(enumerable),有的是不可以枚举的。只获取那些可以枚举的属性,使用Object.keys方法。

Object.keys(Date) // []
hasOwnProperty()

hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。

Date.hasOwnProperty('length')
// true

Date.hasOwnProperty('toString')
// false

hasOwnProperty方法是JavaScript之中唯一一个处理对象属性时,不会遍历原型链的方法。

对象的继承属性
用Object.create方法创造的对象,会继承所有原型对象的属性。

var proto = { p1: 123 };
var o = Object.create(proto);

o.p1 // 123
o.hasOwnProperty("p1") // false

获取所有属性
判断一个对象是否具有某个属性(不管是自身的还是继承的),使用in运算符。

"length" in Date // true
"toString" in Date // true

获得对象的所有可枚举属性(不管是自身的还是继承的),可以使用for-in循环。

var o1 = {p1: 123};

var o2 = Object.create(o1,{
 p2: { value: "abc", enumerable: true }
});

for (p in o2) {console.info(p);}
// p2
// p1

为了在for...in循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下。

for ( var name in object ) {
 if ( object.hasOwnProperty(name) ) {
  /* loop code */
 }
}

获得对象的所有属性(不管是自身的还是继承的,以及是否可枚举),可以使用下面的函数。

function inheritedPropertyNames(obj) {
 var props = {};
 while(obj) {
  Object.getOwnPropertyNames(obj).forEach(function(p) {
   props[p] = true;
  });
  obj = Object.getPrototypeOf(obj);
 }
 return Object.getOwnPropertyNames(props);
}

用法如下:

inheritedPropertyNames(Date)
// ["caller", "constructor", "toString", "UTC", "call", "parse", "prototype", "__defineSetter__", "__lookupSetter__", "length", "arguments", "bind", "__lookupGetter__", "isPrototypeOf", "toLocaleString", "propertyIsEnumerable", "valueOf", "apply", "__defineGetter__", "name", "now", "hasOwnProperty"]

对象的拷贝
如果要拷贝一个对象,需要做到下面两件事情。

确保拷贝后的对象,与原对象具有同样的prototype原型对象。
确保拷贝后的对象,与原对象具有同样的属性。
下面就是根据上面两点,编写的对象拷贝的函数。

function copyObject(orig) {
 var copy = Object.create(Object.getPrototypeOf(orig));
 copyOwnPropertiesFrom(copy, orig);
 return copy;
}

function copyOwnPropertiesFrom(target, source) {
 Object
 .getOwnPropertyNames(source)
 .forEach(function(propKey) {
  var desc = Object.getOwnPropertyDescriptor(source, propKey);
  Object.defineProperty(target, propKey, desc);
 });
 return target;
}

多重继承
JavaScript不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。

function M1(prop) {
 this.hello = prop;
}

function M2(prop) {
 this.world = prop;
}

function S(p1, p2) {
 this.base1 = M1;
 this.base1(p1);
 this.base2 = M2;
 this.base2(p2);
}
S.prototype = new M1();

var s = new S(111, 222);
s.hello // 111
s.world // 222

上面代码中,子类S同时继承了父类M1和M2。当然,从继承链来看,S只有一个父类M1,但是由于在S的实例上,同时执行M1和M2的构造函数,所以它同时继承了这两个类的方法。

Javascript 相关文章推荐
jquery插件制作教程 txtHover
Aug 17 Javascript
JS localStorage实现本地缓存的方法
Jun 22 Javascript
基于jQuery+JSON的省市二三级联动效果
Jun 05 Javascript
Ajax清除浏览器js、css、图片缓存的方法
Aug 06 Javascript
json+jQuery实现的无限级树形菜单效果代码
Aug 27 Javascript
Vuejs第六篇之Vuejs与form元素实例解析
Sep 05 Javascript
JavaScript仿网易选项卡制作代码
Oct 06 Javascript
js前端解决跨域问题的8种方案(最新最全)
Nov 18 Javascript
js canvas实现写字动画效果
Nov 30 Javascript
通过cordova将vue项目打包为webapp的方法
Feb 02 Javascript
微信小程序云开发如何使用云函数生成二维码
May 18 Javascript
js 计算月/周的第一天和最后一天代码
Feb 01 Javascript
JavaScript编程中实现对象封装特性的实例讲解
Jun 24 #Javascript
JS全局变量和局部变量最新解析
Jun 24 #Javascript
jQuery插件passwordStrength密码强度指标详解
Jun 24 #Javascript
jquery选择器中的空格与大于号>、加号+与波浪号~的区别介绍
Jun 24 #Javascript
jquery表单插件Autotab使用方法详解
Jun 24 #Javascript
jQuery插件cxSelect多级联动下拉菜单实例解析
Jun 24 #Javascript
jQuery下拉框的简单应用
Jun 24 #Javascript
You might like
Warning: session_destroy() : Trying to destroy uninitialized sessionq错误
2011/06/16 PHP
无法载入 mcrypt 扩展,请检查 PHP 配置终极解决方案
2011/07/18 PHP
PHP+Mysql+jQuery实现动态展示信息
2011/10/08 PHP
php中current、next与reset函数用法实例
2014/11/17 PHP
Ajax+Json 级联菜单实现代码
2009/10/27 Javascript
jquery 锁定弹出层实现代码
2010/02/23 Javascript
奉献给JavaScript初学者的编写开发的七个细节
2011/01/11 Javascript
TreeView 用法(有代码)(asp.net)
2011/07/15 Javascript
javascript获取设置div的高度和宽度兼容任何浏览器
2013/09/22 Javascript
JavaScript onkeydown事件入门实例(键盘某个按键被按下)
2014/10/17 Javascript
WebApi+Bootstrap+KnockoutJs打造单页面程序
2016/05/16 Javascript
JS HTML5实现拖拽移动列表效果
2020/08/27 Javascript
BootstrapTable请求数据时设置超时(timeout)的方法
2017/01/22 Javascript
jQuery实现大图轮播
2017/02/13 Javascript
jQuery实现单击按钮遮罩弹出对话框效果(1)
2017/02/20 Javascript
详解angular用$sce服务来过滤HTML标签
2017/04/11 Javascript
如何编写一个d.ts文件的步骤详解
2018/04/13 Javascript
IE11下CKEditor在Bootstrap Modal中下拉问题的解决
2019/09/25 Javascript
解决vuex数据页面刷新后初始化操作
2020/07/26 Javascript
vue实现路由懒加载的3种方法示例
2020/09/01 Javascript
antd vue table跨行合并单元格,并且自定义内容实例
2020/10/28 Javascript
[04:49]2014DOTA2国际邀请赛 Newbee顺利挺进总决赛 ImbaTV独家专访
2014/07/19 DOTA
Python通过调用mysql存储过程实现更新数据功能示例
2018/04/03 Python
对python 各种删除文件失败的处理方式分享
2018/04/24 Python
Pandas中Series和DataFrame的索引实现
2019/06/27 Python
各大浏览器 CSS3 和 HTML5 兼容速查表 图文
2010/04/01 HTML / CSS
CSS3制作轮播图的一种方法
2019/11/11 HTML / CSS
简述Html5 IphoneX 适配方法
2018/02/08 HTML / CSS
荷兰鞋类购物网站:Donelli
2019/05/24 全球购物
资深生产主管自我评价
2013/09/22 职场文书
高中军训第一天感言
2014/03/06 职场文书
党员实事承诺书
2014/03/26 职场文书
珠宝的促销活动方案
2014/08/31 职场文书
2015年会计工作总结范文
2015/05/26 职场文书
2016简单的租房合同范本
2016/03/18 职场文书
奖学金发言稿(范文)
2019/08/21 职场文书