关于js对象的属性继承,只要是对js有了解,应该都知道,由于js最初的定位,并没有类似于java、python这类语言关于类的接口。而随着javascript这门语言的广泛使用,越来越多的程序员应用其做复杂的应用开发,面对复杂应用,面向对象的思想随之引入javascript。运用javascript构建对象以及建立对象与对象之间的关系成为当时js程序员着手解决的问题。
当下,在ES6标准中的class语法糖已经很明确的标识出js正是一门面向对象的语言,其模块化的方案也越来越具备构建大型复杂应用程序的能力。但在ES6标准出现之前,又是通过什么样的方式构建对象以及对象之间关系的呢?
在思考这个问题之前,我们首先要理解的是js的类型,js标准中存在array、function、object,而对于function类型来说,它拥有__proto__、prototype、constructor这三个属性。
我们可以试着这样在浏览器中查看:
//object
var s = Object.create({});
console.log(s);
//array
var a = [];
console.log(a);
//function
var f = function(){
}
console.log(f);
对于__proto__、constructor这个是对象特有的一个属性,这也就说明了js中的array以及function可以被当做对象来处理。为此,之前我们可以这样构建一个类:
var testClass = function(){
this.test = 1;
}
testClass.prototype = {
'init': function(){
return 'init';
}
};
var testObj = new testClass();
这是由function类型特有的属性prototype,又称函数原型所构建,prototype的类型必须是一个object,这个object类型可以被继承,也即实例化。以上面代码为例,所谓的实例化就是new运算符,先是创建一个继承自testClass.prototype的新对象,然后执行testClass的constructor属性亦即构造函数,将该函数(testClass)中的一些上下文环境(this)、变量传递给新对象,之后返回这个新对象。
而对于__proto__则是js中的原型链机制,其指向它的上一级直到基本对象。
为此我们可以利用这个原理实现一个简易的function类型的继承。
function extend(target, source){
if(typeof source ! == 'function'){
return function(){};
}
var tmp = new source();
target.prototype = tmp;
target.prototype.constructor = target.prototype.constructor.call(source);
return target;
}
现在我们分析下jquery和zepto中的extend(对象继承):
jquery:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && typeof target !== "function" ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
copy = options[ name ];
// Prevent Object.prototype pollution
// Prevent never-ending loop
if ( name === "__proto__" || target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
src = target[ name ];
// Ensure proper type for the source value
if ( copyIsArray && !Array.isArray( src ) ) {
clone = [];
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
zepto:
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
extend(target[key], source[key], deep)
}
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}
可以看出zepto和jquery在本质上没什么区别,都是浅拷贝和深拷贝的结合,唯一区别就是zepto运用了函数柯里化。
浅拷贝是一个对象的属性应有另一个对象,就像c语言中的指针一样。深拷贝的好处就是实例化原对象,对本对象的某个属性修改不会对原对象的属性修改。对应上面的源码部位就是:target[key]={},和target[key]=[]
总之在jquery和zepto中对象与对象之间的继承就是运用的深拷贝的原理。