(1)javascript对象
var someone = { name:'Tom', age :18, work:'student', showInfo:function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ str += this[name] + ' '; } } console.log(str); } }; someone.showInfo(); //Tom 18 student someone.hobby = 'music'; someone.showInfo(); //Tom 18 student music
从上面可以看出,直接量创建可以通过初始化时将已有对象赋值给它,也可以通过设置给对象添加新的属性,最后通过for/in循环就可以遍历出对象的所有属性,
构造函数方式:
//构造函数创建对象 function CreatPeople(name, age, work) //构造函数 { this.name = name; this.age = age; this.work = work; this.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ str += this[name] + ' '; } } console.log(str); } } var someone = new CreatPeople("Tom", 18, "student"); //实例对象 someone.showInfo(); //Tom 18 student var otherone = new CreatPeople("BoB", 25, "engineer"); otherone.showInfo(); //Bob 25 enginee
对比直接量方式和构造函数方式创建的对象,它们具有相同的属性name, age, work以及方法showInfo,功能相同。其中构造函数方式可以通过函数实现多个不同对象的创建,不过当我们希望给CreatPeole创建的对象添加新的属性habby时,单独每个对象都添加就过于繁琐,为了方便上述过程,js引入不同于C++类方式的继承模式,具体详见下章:原型对象和继承。
(2)原型对象和继承
function CreatPeople(name, age, work){ this.name = name; this.age = age; this.work = work; } CreatPeople.prototype.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ str += this[name] + ' '; } } console.log(str); } function ExtraCreatPeople(name, age, work, hobby){ CreatPeople.apply(this, arguments); this.hobby = hobby; } ExtraCreatPeople.prototype = new CreatPeople(); ExtraCreatPeople.prototype.data = 1; ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", 18, "student"); someone.showInfo(); //Tom 18 student var otherone = new ExtraCreatPeople("Bob", 18, "student", "Sing"); otherone.showInfo(); //Bob 18 student sing 1
通过子类原型继承,生成了一个新的构造函数,实现了对于原构造函数的扩展。不过如果仔细看上面的代码,我们就会发现ExtraCreatePeople创建的对象时会调用两次new,这就造成了内存的浪费,同时也影响了效率。上面我们提到过,每一个函数都有一个原型对象,通过原型链由构造函数创建的对象具有原型对象的属性和方法,那么我们将CreatePerson的的原型对象直接借用给ExtraCreatePerson,是不是也可以实现目的呢?
function CreatPeople(name, age, work){ this.name = name; this.age = age; this.work = work; } CreatPeople.prototype.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ str += this[name] + ' '; } } console.log(str); } function ExtraCreatPeople(name, age, work, hobby){ CreatPeople.apply(this, arguments); this.hobby = hobby; } ExtraCreatPeople.prototype = CreatPeople.prototype; ExtraCreatPeople.prototype.data = 1; ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", 18, "student"); someone.showInfo(); //Tom 18 student 1 var otherone = new ExtraCreatPeople("Bob", 18, "student", "Sing"); otherone.showInfo(); //Bob 18 student sing 1
通过借用原型链,也实现了相同的功能,不过仔细观察,就会发现借用原型引入了新的问题,新构造函数会污染原有构造函数的原型链,从而给原构造函数添加了新的属性,这与我们上面提到的在不影响原有对象的基础上创建新构造函数的目的是不符合,在通过对比上面的new引用,我们发现new方式实现的新构造函数并不会污染原有原型链,结合以上两种模式,就实现下面的构造函数创建:
function CreatPeople(name, age, work){ this.name = name; this.age = age; this.work = work; } CreatPeople.prototype.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ str += this[name] + ' '; } } console.log(str); } function F(){ } F.prototype = CreatPeople.prototype; function ExtraCreatPeople(name, age, work, hobby){ CreatPeople.apply(this, arguments); this.hobby = hobby; } ExtraCreatPeople.prototype = new F(); ExtraCreatPeople.prototype.data = 1; ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", 18, "student"); someone.showInfo(); //Tom 18 student var otherone = new ExtraCreatPeople("Bob", 18, "student", "Sing"); otherone.showInfo(); //Bob 18 student sing 1
通过中间对象,实现了新构造函数与原构造函数的隔离,不过我们看下面的代码:
function CreatPeople(name, age, work){ this.name = name; this.age = age; this.work = work; } CreatPeople.prototype.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ if(typeof(this[name]) == "object"){ str += this[name].name + ' '; }else{ str += this[name] + ' '; } } } console.log(str); } CreatPeople.prototype.child = { name : "Lucy" } function F(){ } F.prototype = CreatPeople.prototype; function ExtraCreatPeople(name, age, work, habit){ CreatPeople.apply(this, arguments); this.habit = habit; } ExtraCreatPeople.prototype = new F(); ExtraCreatPeople.prototype.data = 1; ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", 18, "student"); someone.showInfo(); //Tom 18 student Lucy var otherone = new ExtraCreatPeople("Bob", 18, "student", "Sing"); otherone.showInfo(); //Bob 18 student sing 1 Lucy someone.child.name = "pop"; someone.showInfo(); //Tom 18 student pop otherone.showInfo(); //Bob 18 student sing 1 pop
在这里,很容易发现someone中对原型链上数据的修改导致otherone中的对应属性也发生了改变,这种错误隐蔽性高,不易发现,因此需要着重理解和掌握!不过通过上述了解,我们发现原型链的借用就是可以理解为对象的复制过程,就原构造函数对象的参数赋值给新的构造函数,同时两个构造函数对象不会互相影响,这里就要理解JS中对象中比较复杂的部分、浅拷贝和深拷贝,具体详见下段:对象的克隆。
3.对象的克隆
对象的克隆分为两种方式,浅度克隆:原始类型为值传递,对象类型仍为引用传递。深度克隆:所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中。从这两部分可以看出来,上面的原型引用就是浅度克隆,为了对原型继承的要求,深度克隆就满足我们的要求,如下:
function isClass(obj){ if(obj == null) return "NULL"; if(obj == undefined) return "Undefiend"; return Object.prototype.toString.call(obj).slice(8, -1); } function deepclone(obj){ var result; var objClass = isClass(obj); //确定result的类型 if(objClass==="Object"){ result={}; }else if(objClass==="Array"){ result=[]; }else{ return obj; } for(key in obj){ var copy=obj[key]; if(isClass(copy)=="Object"){ result[key]=arguments.callee(copy); //递归调用 }else if(isClass(copy)=="Array"){ result[key]=arguments.callee(copy); }else{ result[key]=obj[key]; } } return result; } function CreatPeople(name, age, work){ this.name = name; this.age = age; this.work = work; } CreatPeople.prototype.showInfo = function(){ var str = ""; for(var name in this){ if(typeof(this[name]) != "function"){ if(typeof(this[name]) == "object"){ str += this[name].name + ' '; }else{ str += this[name] + ' '; } } } console.log(str); } CreatPeople.prototype.child = { name : "Lucy" } function ExtraCreatPeople(name, age, work, habit){ CreatPeople.apply(this, arguments); this.habit = habit; } ExtraCreatPeople.prototype = deepclone(CreatPeople.prototype); ExtraCreatPeople.prototype.data = 1; ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", 18, "student"); someone.showInfo(); //Tom 18 student Lucy var otherone = new ExtraCreatPeople("Bob", 18, "student", "Sing"); otherone.showInfo(); //Bob 18 student sing Lucy 1 someone.child.name = "pop"; someone.showInfo(); //Tom 18 student pop otherone.showInfo(); //Bob 18 student sing Lucy 1
到此为止,子类和父类实现了完全的隔离,这也是实现原型继承最佳的方式。