web前端学习(二) javascript对象和原型继承

时间:2023-11-09 17:44:44
目录

1. JavaScrpt对象

2. 原型对象和继承

3. 对象的克隆

(1)javascript对象

  在JS中,对象是属性的容器。对于单个对象来说,都由属性名和属性值构成;其中属性名需要是标识符,而不能是字符串或者表达式,属性值可以是除undefined外的任何值。如果对象的属性值是函数,那么这个属性通常被称为方法。一般来说,对象可以通过直接量方式或者构造函数方式创建的,那么下面我用代码的方式更直观的展示对象的实现方式。(console.log在IE中无法工作,需要firefox或者Chrome浏览器方能正确显示)
  直接量方式:
var someone = {
name:'Tom',
age :,
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", , "student"); //实例对象
someone.showInfo(); //Tom 18 student var otherone = new CreatPeople("BoB", , "engineer");
otherone.showInfo(); //Bob 25 enginee

  对比直接量方式和构造函数方式创建的对象,它们具有相同的属性name, age, work以及方法showInfo,功能相同。其中构造函数方式可以通过函数实现多个不同对象的创建,不过当我们希望给CreatPeole创建的对象添加新的属性habby时,单独每个对象都添加就过于繁琐,为了方便上述过程,js引入不同于C++类方式的继承模式,具体详见下章:原型对象和继承。

(2)原型对象和继承

   JavaScript中每个对象都有原型对象,原型对象的所有属性都是以它为原型的对象的属性,也就是说,每个对象都继承原型对象的所有属性。JS中所有函数对象都有Prototype属性,它引用一个对象(原型对象), 虽然原型对象初始化时是空的,但你在其中定义的任何属性都会被该构造函数创建的对象继承。特别注意一点:被创建对象的继承是在查询时自动发生的,而并非直接复制,这样就大量减少了内存占有。类似与直接量添加属性的方式,我们也可以给函数通过prototype属性添加属性,此时由构造函数创建的实例对象就继承原构造函数的Prototype引用对象的全部属性。不过如果不希望修改原有对象,而只是借用已存在的构造函数相关接口,来实现新的构造函数,就需要使用JavaScript的子类化继承方式,实践如下:设计一个新构造函数ExtraCreatPerson,它继承了原有属性和方法,又在原有构造函数的基础上增加了hobby属性,从而实现对原有接口的扩展:
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 = ;
ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", , "student");
someone.showInfo(); //Tom 18 student var otherone = new ExtraCreatPeople("Bob", , "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 = ;
ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", , "student");
someone.showInfo(); //Tom 18 student 1 var otherone = new ExtraCreatPeople("Bob", , "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 = ;
ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", , "student");
someone.showInfo(); //Tom 18 student var otherone = new ExtraCreatPeople("Bob", , "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 = ;
ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", , "student");
someone.showInfo(); //Tom 18 student Lucy var otherone = new ExtraCreatPeople("Bob", , "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(, -);
} 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 = ;
ExtraCreatPeople.prototype.constructor = ExtraCreatPeople; var someone = new CreatPeople("Tom", , "student");
someone.showInfo(); //Tom 18 student Lucy var otherone = new ExtraCreatPeople("Bob", , "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

  到此为止,子类和父类实现了完全的隔离,这也是实现原型继承最佳的方式。