JS语言理解03 非构造函数的继承

JavaScript中非构造函数的继承

什么是”非构造函数”的继承?

让一个对象去继承一个不相关对象,由于这两个对象都是普通对象,不是构造函数,所以无法使用构造函数方法实现继承。

object()方法

JSON格式的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点。

1
2
3
4
5
6
function object(o) {    
function F() {}    
F.prototype = o;  
// child.__proto__ = F.prototype = o
return new F();  
}

这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。

使用的时候,第一步先在父对象的基础上,生成子对象:

1
var Doctor = object(Chinese);

实现了子对象对父对象的继承。

ES6中的Object.create方法的原理就是这样。

Object.create()方法

Object.create是直接通过原型,而非模拟类,来实现普通对象(非构造函数)之间的继承。ES6之后这也是最常用的实现普通对象继承的方法

1
let child = Object.create(father, descriptors);

这样就可以实现child与father的继承,继承关系是通过child.__proto__ = father实现的,再通过描述符对象descriptors添加属于自己的属性

实际上,可以把上面object()方法看做Object.create方法的简易Pollyfill

Object.setPrototypeOf()方法

Object.setPrototypeOf()Object.create都是更改原型链的手段,Object.getPrototypeOf(a)是用来获取对象的__proto__属性

Object.setPrototypeOf(a, b)实现的是a.__proto__ = b,最常用来避免直接操作对象的非标属性__proto__

1
Object.setPrototypeOf(child, father)

注意的是,更改__proto__性能很差,应该避免设置一个对象的[[Prototype]],应该使用 Object.create()来创建你想要的[[Prototype]]的新对象

浅拷贝

除了prototype链的思路之外,可以通过对象浅拷贝的方法实现继承:

1
2
3
4
5
6
7
8
function extendCopy(p) {    
var c = {};    
for (var i in p) {      
c[i] = p[i];    
}    
c.uber = p;    
return c;  
}

浅拷贝的问题就是如果父对象的属性等于数组或另一个对象,那么子对象获得的就是指向这个数组或对象的指针,因此存在父对象被篡改的可能。

深拷贝

深拷贝可以实现真正意义上的数组和对象的拷贝,原理是递归调用浅拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
function deepCopy(p, c) {    
var c = c || {};    
for (var i in p) {      
if (typeof p[i] === 'object') {        
c[i] = (p[i].constructor === Array) ? [] : {};        
deepCopy(p[i], c[i]);      
} else {         
c[i] = p[i];      
}    
}    
return c;  
}

之所以不用判断p的类型是对象还是数组,原因出在for...in上,会遍历p的原型上的属性,具有所有数组原型属性的对象,就是数组了。

==一次面试的时候,发现下面的这个函数有些问题,之前没有注意到==

在进行这个判断typeof p[i] === 'object'时,对JSON/Math/RegExp对象是有问题的,问题还是出现在了对象判断上,还是使用Object.prototype.toString.call判断更准确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
const type = Object.prototype.toString.call(p[i]).match(/\s(.+)\]/)[1].toLowerCase();
switch (type) {
case 'array': {
c[i] = [];
deepCopy(p[i], c[i]);
break;
}
case 'object': {
c[i] = {};
deepCopy(p[i], c[i]);
break;
}
default: {
c[i] = p[i];
break;
}
}
}
return c;
}

jQuery的extend方法第一个参数默认为false,此时实现的是浅拷贝,如果为true则实现的其实就是上述的深拷贝。

1
jQuery.extend([deep], target, object1[, objectN])

参考