你的位置:首页 > Java教程

[Java教程]js 对象深复制,创建对象和继承


  js 对象深复制,创建对象和继承。主要参考高级编程第三版,总结网上部分资料和自己的代码测试心得。每走一小步,就做一个小结。

1.对象/数组深复制

  一般的=号传递的都是对象/数组的引用,如在控制台输入

var a=[1,2,3],  b=a;b[0]=0;a[0]

  此时显示的结果为0,也就是说a和b指向的是同一个数组,只是名字不一样罢了。

 

  单层深复制:

  1.js的slice函数:

  返回一个新的数组,包含下标从 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函数只是单层复制。类似原型继承(见下文),基本类型的属性复制了(有自己的副本),引用类型的属性指向了同一个引用。

 

  2.concat函数

  用于连接两个或多个数组。不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

  同样用上面的例子测试,只是改动第二句

b=a.concat([]);

  可以看到一样的结果。

 

  3.c=$.extend({}, {}, b)  (jquery的extend方法)

  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并没有变化。因此,这种方法写插件参数的时候,在插件里面对引用型参数的改变会反馈到传入的相应参数上,小伙伴们注意咯!(不过一般貌似也不会在里面改参数吧?)

 

  多层深复制

  1.网上摘录的代码,用递归实现层层基本类型属性的复制。

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里面用过,确实写得简单易懂。

 

  2.使用new操作符,以构造函数的形式实现多层深复制

  不得不承认,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影响。好吧,我承认,其实这就是构造函数模式而已。管他呢,理解了,能用就行!

2.创建对象

  讨厌的设计模式来了。。。说不定什么时候能喜欢上这些呢?毕竟是前辈们的结晶。

  (摘自高级编程第三版)

  1.确定原型和实例的关系

//b是a的原型 a instanceof b b.prototype.isPrototypeOf(a)

  注意construtor针对的是构造函数。

  2.工厂模式:在内部创建对象

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

  3.构造函数模式:

function Person(name) {  this.name = name;  this.sayName = function() {    alert(this.name);  };}var person = new Person('codetker'); //能判断类型person.sayName();

  缺点:实例会拥有多余的属性(每个实例均新创建一次所有方法)

  4.原型模式:

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方法。

  原型模式缺点:
  实例和构造函数没关系,而和原型有松散关系。但是前面的实例可能修改了原型导致后面的实例不好受。实例应该有属于自己的全部属性。

  5.组合使用构造函数模式和原型模式:分开写

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);//一般插件的形式

  6.动态原型模式:将所有信息封装在构造函数中,在构造函数中初始化原型

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);

  7.寄生构造函数模式:在工厂模式的基础之上使用new,返回的对象在构造函数和构造函数的原型属性之间没有任何关系

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判断对象类型

  8.稳妥构造函数模式:不使用new,不引用this,私有变量外部无法访问,仅暴露方法

//应用于安全的环境中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判断对象类型

3.继承

  听起来很高大上的样子!其实,,,还是挺高大上的。。。

  1.原型链继承

function Super() {  this.property = true;}Super.prototype.getValue = function() {  return this.property;};function Sub() {  this.sub = false;}//继承,创建Super的实例,并将实例的原型赋给Sub的原型。即用Super的实例重写了Sub的原型对象Sub.prototype = new Super();//原型上添加方法(一定要放在替换原型的语句之后,不然就miss)Sub.prototype.getSub = function() {  return this.sub;};//实例var instance = new Sub();console.log(instance.getValue());

  缺点:
  1.通过原型实现继承的时候,原型实际上会变成另一个类型的实例,于是原来的实例属性就变成了现在的原型属性了。即第二个实例会受到第一个实例的影响
  2.没有办法在不影响所有对象的情况下,给超类的构造函数传递参数

  2.借用构造函数实现继承(伪造对象/经典继承)

function Super(name) {  this.color = ['red', 'blue'];  this.name = name;  this.sayName = function() {    console.log(this.name);  };}Super.prototype.say = function() {  console.log('not seen');}function Sub(name2) {  //继承了Super,在子类型构造函数的内部调用超类型的构造函数,从而执行了Super()中定义的初始化代码  Super.call(this, name2); //可以在子类型的构造函数里面给超类传递参数}var instance = new Sub('codetker'); //实例之间不冲突,拥有自己的属性console.log(instance.color);console.log(instance.name);instance.sayName();instance.say(); //not a function

  缺点:

  类似构造函数的问题,方法都在构造函数中定义,外面无法定义
  超类中原型定义的方法,对子类型都不可见

  3.组合继承

function Super(name) {  this.color = ['red', 'blue'];  this.name = name;  this.sayName = function() {    console.log(this.name);  };}Super.prototype.say = function() {  console.log(this.name);}function Sub(name2, age) {  //继承属性  Super.call(this, name2); //调用一次  this.age = age;}Sub.prototype = new Super(); //调用一次,继承方法Sub.prototype.constructor = Super;var instance = new Sub('codetker', 21); //实例之间不冲突,拥有自己的属性instance.say(); //OK now

  缺点:无论什么情况下,都会调用两次超类型构造函数

  4.原型式继承

function object(Super) { //浅复制了Super  function F() {} //临时性构造函数  F.prototype = Super;  return new F();}var person = {  name: 'codetker',  friends: ['a', 'b']};var person2 = object(person);person2.name = 'code';person2.friends.push('c');console.log(person2.name);console.log(person.name); //name没变(基本类型),用于创建类似对象console.log(person2.friends);console.log(person.friends); //friends变了(引用类型)

  ES5用Object.create()方法规范化了原型继承,只有一个参数的时候同object(),而两个参数的时候后面的参数为传入的属性,如Object.create(person,{name:{value:'TK'}});

  5.寄生式继承

function object(Super) { //浅复制了Super  function F() {} //临时性构造函数  F.prototype = Super;  return new F();}function create(o) {  var clone = object(o); //通过调用函数创建一个对象  clone.sayHi = function() { //以某种方式来增强这个对象    alert('Hi!');  };  return clone;}var person = {  name: 'codetker',  friends: ['a', 'b']};var another = create(person);another.sayHi();

  6.寄生组合式继承

//不必为了指定子类型的原型而调用超类型的构造函数(YUI.lang.extend()采用寄生组合继承)function object(Super) { //浅复制了Super  function F() {} //临时性构造函数  F.prototype = Super;  return new F();}function inheritPrototype(sub, super) {  var prototype = object(super.prototype); //创建对象,超类型原类型的副本  prototype.constructor = sub(); //增强对象,为副本添加constructor属性  sub.prototype = prototype; //指定对象,赋值}function Super(name) {  this.color = ['red', 'blue'];  this.name = name;  this.sayName = function() {    console.log(this.name);  };}Super.prototype.say = function() {  console.log(this.name);}function Sub(name2, age) {  //继承属性  Super.call(this, name2); //调用一次  this.age = age;}inheritPrototype(Sub, Super);Sub.prototype.sayAge = function() {  alert(this.age);};

  感觉内容不少,完全属于自己的却不多。。。不过高级编程第三版确实讲得很详细,且做分享吧~