当前位置: 首页 > Web前端 > HTML5

程序员好技术文档HTML5开发中JS的创建与继承

时间:2023-04-05 00:14:31 HTML5

程序员好技术文档HTML5开发JS的创建与继承,JavaScript会为每一个创建的对象设置一个原型,指向其原型对象。  当我们使用obj.xxx访问一个对象的属性时,JavaScript引擎首先在当前对象上寻找属性,如果没有找到,就去它的原型对象,如果找不到,就去所有的回到对象。prototype对象,最后如果没有找到,只能返回undefined。  例如创建一个Array对象:  vararr=[1,2,3];  其原型链为:  arr---->Array.prototype---->Object.prototype---->null  Array.prototype定义了indexOf()、shift()等方法,因此您可以直接在所有Array对象上调用这些方法。  当我们创建一个函数时:  functionfoo(){  return0;  }  函数也是一个对象,它的原型链是:  foo---->Function.prototype---->Object.prototype---->null  由于Function.prototype定义了apply()等方法,所有的函数都可以调用apply()方法。  很容易认为,如果原型链很长,那么访问一个对象的属性会比较慢,因为查找需要更多的时间,所以要注意原型链不要太长。  构造函数  除了直接使用{...}来创建对象之外,JavaScript还可以使用构造函数方法来创建对象。它的用法是先定义一个构造函数:  functionStudent(name){  this.name=name;  this.hello=function(){  alert('Hello,'+this.name+'!');  }  }  你会问,咦,这不是一个普通的函数吗?  这确实是一个普通的函数,但是在JavaScript中,你可以使用关键字new来调用这个函数并返回一个对象:  varxiaoming=newStudent('小明');  xiaoming.name;//'小明'  xiaoming.hello();//你好,小明!  注意,如果不写new,这是一个返回undefined的普通函数。但是,如果你写new,它就变成了一个构造函数,它绑定的this指向新创建的对象,默认返回this,也就是说最后不用写returnthis;  新创建小明的原型链为:  xiaoming---->Student.prototype---->Object.prototype---->null  即小明的原型指向函数Student的原型。如果再创建xiaohong和xiaojun,这些对象的原型和xiaoming一样:  xiaoming↘  xiaohong-→Student.prototype---->Object.prototype---->null  xiaojun↗  用newStudent()创建的对象也从原型中获取了一个constructor属性,指向函数Student本身:  xiaoming.constructor===Student.prototype.constructor;//true  Student.prototype.constructor===Student;//true  Object.getPrototypeOf(xiaoming)===Student.prototype;//true  xiaominginstanceofStudent;//true  ?用一张图来表示这些乱七八糟的关系:  红色箭头是原型链。注意Student.prototype指向的对象是小明和小红的原型对象。这个原型对象还有一个属性构造函数,它指向Student函数本身。  另外,函数Student恰好有一个属性prototype指向xiaoming和xiaohong的原型对象,但是像xiaoming和xiaohong这样的对象没有prototype属性,不过可以使用__proto__的非标准用法来检查。  现在我们认为像xiaoming和xiaohong这样的对象“继承”自Student。  不过还有一个小问题,注意观察:  xiaoming.name;//'小明'  xiaohong.name;//'小红'  xiaoming.hello;//函数:Student.hello()  xiaohong.hello;//函数:Student.hello()  xiaoming.hello===xiaohong.hello;//false  xiaoming和xiaohong名字不一样,是对的,不然我们就分不清谁是谁了。  小明和小红的hello是一个函数,但是是两个不同的函数,虽然函数名和代码是一样的!  如果我们通过newStudent()创建了很多对象,那么对象的hello函数其实只需要共享同一个函数,这样可以节省很多内存。  让创建的对象共享一个hello函数,根据对象属性查找的原则,我们只需要将hello函数移动到小明和小红的共同原型,即Student.prototype中即可:  修改代码如下:  functionStudent(name){  this.name=name;  }  Student.prototype.hello=function(){  alert('Hello,'+this.name+'!');  };  使用new创建基于原型的JavaScript对象就是这么简单!  忘记写new怎么办  如果一个函数定义为Constructor,但是调用的时候忘记写new怎么办?  在严格模式下,this.name=name会报错,因为this绑定了undefined。在非严格模式下,this.name=name不会报错,因为this绑定了window,所以无意中创建了全局变量name。并返回undefined,这更糟糕。  所以调用构造函数的时候不要忘记写new。为了区分普通函数和构造函数,按照约定,构造函数的首字母要大写,而普通函数的首字母要小写,这样一些语法检查工具比如jslint会帮你检测缺少新的。  最后,我们还可以写一个createStudent()函数,在内部封装所有新的操作。一个常见的编程模式如下所示:  functionStudent(props){  this.name=props.name||'匿名的';//默认值为'anonymous'  this.grade=props.等级||1;//默认值为1  }  Student.prototype.hello=function(){  alert('Hello,'+this.name+'!');  };  functioncreateStudent(props){  returnnewStudent(props||{})  }  这个createStudent()函数有几个很大的优点:首先,它不需要newtocall,二是参数很灵活,可以不传,也可以这样传:  varxiaoming=createStudent({  name:'Xiaoming'  });  xiaoming.grade;//1  如果创建的对象有很多属性,我们只需要传递一些需要的属性,其余的属性可以使用默认值。由于参数是一个对象,我们不需要记住参数的顺序。如果刚好从JSON中获取对象,可以直接创建xiaoming。  继承  在Java、C++等传统的Class-based语言中,继承的本质是扩展一个已有的Class,生成一个新的Subclass。  由于这类语言严格区分类和实例,所以继承实际上是类型的扩展。但是,由于JavaScript使用原型继承,我们不能直接扩展一个Class,因为没有Class这样的类型。  不过还是有办法的。我们先回顾一下Student的构造函数:  functionStudent(props){  this.name=props.name||'未命名';  }  Student.prototype.hello=function(){  alert('你好,'+this.name+'!');  }  和Student的原型链:  现在我们要基于Student扩展PrimaryStudent,可以先定义PrimaryStudent:  functionPrimaryStudent(props){  //调用学生构造函数并绑定此变量:  Student.call(this,props);  this.grade=props.grade||1;  }  不过,调用Student构造函数并不代表继承Student。PrimaryStudent创建的对象原型为:  newPrimaryStudent()---->PrimaryStudent.prototype---->Object。prototype---->null  必须想办法修改原型链为:  newPrimaryStudent()---->PrimaryStudent.prototype---->Student.prototype---->Object.prototype---->null  这样原型链就对了,继承关系就对了。基于PrimaryStudent创建的新对象既可以调用PrimaryStudent.prototype定义的方法,也可以调用Student.prototype定义的方法。  如果你想用最简单粗暴的方式做到这一点:  PrimaryStudent.prototype=Student.prototype;  决不!如果是这样,PrimaryStudent和Student共享一个原型对象,那么为什么要定义PrimaryStudent?  我们必须使用一个中间对象来实现正确的原型链。这个中间对象的原型必须指向Student.prototype。为了实现这一点,参考道爷(发明JSON的Douglas)的代码,中间对象可以通过一个空函数F来实现:  //PrimaryStudent构造函数:  functionPrimaryStudent(props){  Student.call(this,道具);  this.grade=props.grade||1;  }  //空函数F:  functionF(){  }  //将F的原型指向Student.prototype:  F.prototype=Student。原型;  //将PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:  PrimaryStudent.prototype=newF();  //固定PrimaryStudent原型的构造函数为PrimaryStudent:  PrimaryStudent.prototype.constructor=PrimaryStudent;  //继续在PrimaryStudent原型(即newF()对象)定义方法:  PrimaryStudent.prototype.getGrade=function(){  returnthis.grade;  };  //创建小明:  varxiaoming=newPrimaryStudent({  name:'小明',  成绩:2  });  xiaoming.name;//'小明'  xiaoming.grade;//2  //验证原型:  xiaoming.__proto__===PrimaryStudent.prototype;//真  xiaoming.__proto__.__proto__===Student.prototype;//true  //验证继承关系:  xiaominginstanceofPrimaryStudent;//true  xiaominginstanceofStudent;//true  用一个Figure表示新的原型链:  注意函数F只是用来桥接的,我们只新建一个F()实例,原来Student定义的原型链有没有被改变。  如果用inherits()函数封装继承的动作,还可以隐藏F的定义,简化代码:  functioninherits(Child,Parent){  varF=function(){};  F.prototype=Parent.prototype;  Child.prototype=newF();  Child.prototype.constructor=Child;  }  这个inherits()函数可以被重用:  functionStudent(props){  this.name=props.name||'未命名';  }  Student.prototype.hello=function(){  alert('Hello,'+this.name+'!');  }  functionPrimaryStudent(props){  Student.call(this,props);  this.grade=props.grade||1;  }  //实现原型继承链:  inherits(PrimaryStudent,Student);  //为PrimaryStudent原型绑定其他方法:  PrimaryStudent.prototype.getGrade=function(){  returnthis.grade;  };  Summary  JavaScript的原型继承实现方法是:  定义一个新的构造函数,并在内部调用call()希望“继承”这个构造函数,并绑定this;  原型链继承是借助中间函数F实现的,最好是通过封装的inherits函数;  继续在新构造函数的原型上定义新的方法。  ES6类继承  新关键字class从ES6开始正式引入JavaScript。类的目的是使定义类更容易。  先回顾一下用函数实现Student的方法:  functionStudent(name){  this.name=name;  }  Student.prototype.hello=function(){  alert('你好,'+this.name+'!');  }  如果用新的class关键字写Student,可以这样写:  classStudent{  constructor(name){  this.name=name;  }  hello(){  alert('Hello,'+this.name+'!');  }  }  对比一下可以发现class的定义包括原型对象上定义的构造函数和函数hello()(注意这里没有function关键字),从而避免了Student.prototype.hello=function(){...}这样零散的代码。  最后创建Student对象的代码和上一章一模一样:  varxiaoming=newStudent('Xiaoming');  xiaoming.hello();  classinheritanceAnother对象的巨大好处是继承更方便。想一想我们需要编写多少代码才能从Student派生PrimaryStudent。现在,原型继承的中间对象,原型对象的构造函数等都不需要考虑了,直接通过extends实现即可:{  super(名字);//记得用super调用父类的构造函数!  this.grade=等级;  }  myGrade(){  alert('我在年级'+this.grade);  }  }  注意PrimaryStudent的定义也是通过class关键字实现的,extends表示原型链对象来自Student.子类的构造函数可能与父类的构造函数不同。比如PrimaryStudent需要name和grade作为两个参数,需要用super(name)调用父类的构造函数,否则无法正常初始化父类的name属性。  PrimaryStudent已经自动获取了父类Student的hello方法,我们在子类中定义了一个新的myGrade方法。  ES6引入的类和原来的JavaScript原型继承有什么区别?事实上,它们之间没有区别。class的作用是让JavaScript引擎实现我们原本需要编写的原型链码。总之,使用class的好处就是大大简化了原型链码。