js 对象深复制,创建对象和继承。主要参考高级编程第三版,总结网上部分资料和自己的代码测试心得。每走一小步,就做一个小结。
一般的=号传递的都是对象/数组的引用,如在控制台输入
var a=[1,2,3], b=a;b[0]=0;a[0]
此时显示的结果为0,也就是说a和b指向的是同一个数组,只是名字不一样罢了。
返回一个新的数组,包含下标从 start 到 end (不包括该元素,此参数可选)的元素。
控制台输入:
var a=[1,2,3], b=a.slice(0);b[0]=5;a
返回的a并没有变,说明b是a的副本,修改副本对a没有影响。
然后输入一下代码:
var a=[[1,4],2,3], b=a.slice(0);b[0][1]=5;a
可以看到a的值变了。说明slice函数只是单层复制。类似原型继承(见下文),基本类型的属性复制了(有自己的副本),引用类型的属性指向了同一个引用。
用于连接两个或多个数组。不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
同样用上面的例子测试,只是改动第二句
b=a.concat([]);
可以看到一样的结果。
1和2两个是百度上搜索的,自己验证了一下。第三个是看js OOP的时候忽然想到的,jq的extend是多层深复制么?
首先是jquery.1.11.0的extend源码
jQuery.extend = jQuery.fn.extend = function() { var src, copyIsArray, copy, name, options, 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" && !jQuery.isFunction(target) ) { 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 ) { src = target[ name ]; copy = options[ name ]; // PRevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // 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;};
注意标红的那一句,继承基本对象,里面的实现是用in遍历属性,很明显如果是引用对象肯定也是复制引用了,并非深层对象的副本。我们来测试一下:
var a=[1,2,3], b=[2,3,4], d=$.extend( a, b);d[0]=5;a
其实此时返回的d就是a的别名,指向同一个数组。这句话只是让a去继承b的属性。于是我们可以变一下
var a=[1,2,3], b=[2,3,4], e=$.extend({}, b);
这时候的e也就是b的一个副本了,相当于用b的属性扩充了{},然后e指向扩充后了的{}。这样的话,一般插件里面用传递的参数覆盖默认的参数的写法
c=$.extend({}, a, b);
也就不难理解了,毕竟只是改了{},再次调用插件的时候里面的默认参数a还是没有变滴!
接下来是重点,用二维数组测试
var a=[[1,2],2,3], f=$.extend({}, a);f[0][0]=5;a[0]
发现改变f[0][0],a[0][0]也跟着变了!如果extend有多个参数的时候,如
var a=[[1,2],2,3], b=[[2,3,4],4,6], g=$.extend({}, a, b);g[0][0]=5;b[0]
可以发现b跟着变了,而测试a可以看到a并没有变化。因此,这种方法写插件参数的时候,在插件里面对引用型参数的改变会反馈到传入的相应参数上,小伙伴们注意咯!(不过一般貌似也不会在里面改参数吧?)
function getType(o) { var _t; return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase(); } function extend(destination,source) { for(var p in source) { if(getType(source[p])=="array"||getType(source[p])=="object") { destination[p]=getType(source[p])=="array"?[]:{}; arguments.callee(destination[p],source[p]); //递归调用在这里 } else { destination[p]=source[p]; } } }
这个我在前面的AntSystem里面用过,确实写得简单易懂。
不得不承认,new是一个很神奇的操作符,虽然这样做可能有些繁琐。
function newF(){ var a=0, b=[5,[4,5]]; this.name="codetker"; this.a=a; this.b=b;}var temp=new newF();temp.a=5;temp.b[1][0]=6;var temp2=new newF();temp2.atemp2.b[1][0]
可以看到temp2的a和b[1][0]都没有被temp影响。好吧,我承认,其实这就是构造函数模式而已。管他呢,理解了,能用就行!
讨厌的设计模式来了。。。说不定什么时候能喜欢上这些呢?毕竟是前辈们的结晶。
(摘自高级编程第三版)
//b是a的原型 a instanceof b b.prototype.isPrototypeOf(a)
注意construtor针对的是构造函数。
function createPerson(name) { var o = new Object(); o.name = name; o.sayName = function() { alert(this.name); }; return o;}var person = createPerson('codetker');person.sayName();
缺点:无法知道对象的类型。也就是1里面的判断为false
function Person(name) { this.name = name; this.sayName = function() { alert(this.name); };}var person = new Person('codetker'); //能判断类型person.sayName();
缺点:实例会拥有多余的属性(每个实例均新创建一次所有方法)
function Person() {}Person.prototype.name = 'codetker'; //将属性和方法都写在了构造函数的原型上Person.prototype.sayName = function() { alert(this.name);};var person = new Person(); //建立了实例和Person.prototype之间的连接(person._proto_ FF/Chrome/Safari or person.[[prototype]] in ES5)person.sayName();
在这里面,可以用Person.prototype.isPrototypeOf(person) or Object.getPrototypeOf(person)==Person.prototype来确认是否为原型。用hasOwnProterty()判断对象实例属性和原型属性。
in操作符可以在通过对象能够访问属性时返回true,因此结合property in object与hasOwnProperty(object,property)可以判断属性到底是存在于对象中,还是存在于原型中。如
function hasprototypeProperty(object,name){ return !object.hasOwnProperty(name) && (name in object);}
另外,对象的原型可以用对象字面量简写,如
Person.prototype = { constructor: Person, //如果想用constructor的话 name: 'codetker', sayName: function() { alert(this.name); }}; //相当于创建新对象,因此constructor不指向Person。如果在之前new一个实例,则实例取不到Person.prototype修改后的内容
问题也来了,这样相对于重写了默认的prototype对象,因此constructor属性也变了。如果需要constructor,可以像上面手动设置一下,不过这样的constructor属性就会被默认为可枚举的。要改成一模一样,可以用Object.defineProperty方法。
原型模式缺点: 实例和构造函数没关系,而和原型有松散关系。但是前面的实例可能修改了原型导致后面的实例不好受。实例应该有属于自己的全部属性。
function Person(name, age) { //每个实例都有自己的属性 this.name = name; this.age = age; this.friends = ['a', 'b'];}person.prototype = { //所有的实例共用原型的方法 constructor: Person, sayName: function() { alert(this.name); }};var person = new Person('codetker', 21);//一般插件的形式
function Person(name, age) { //每个实例都有自己的属性 this.name = name; this.age = age; this.friends = ['a', 'b']; //方法 if (typeof this.sayName != 'function') { Person.prototype.sayName = function() { alert(this.name); }; }}var person = new Person('codetker', 21);
function createPerson(name) { var o = new Object(); o.name = name; o.sayName = function() { alert(this.name); }; return o;}var person =new createPerson('codetker');person.sayName();//person与Person以及Person.prototype之间没有联系,不能用instanceof判断对象类型
//应用于安全的环境中function createPerson(name) { var o = new Object(); //这儿可以定义私有变量 o.sayName = function() { alert(name); }; return o;}var person =createPerson('codetker');person.sayName();//仅能通过sayName()方法访问//person与Person以及Person.prototype之间没有联系,不能用instanceof判断对象类型
听起来很高大上的样子!其实,,,还是挺高大上的。。。
function Super() { this.property = true;}Super.prototype.getValue = function() { return this.property;};function Sub() { t
新闻热点
疑难解答