在面向对象前篇中提到过原型的概念,说到原型,便又延伸出了关于原型的一个知识点——原型链。也许作为一名专业的前端开发人员来说,明白原型链的含义以及用法,但对于前端的爱好者和在校大学生而言,对于原型链的概念可能是第一次听说,所以,今天就和大家学习一下原型链。
一、含义
实例对象和原型对象组成的一条链式结构,链式结构之间通过proto链接起来,这条链就成为原型链。
例:
function Person(name){
this.name=name;
}
Person.prototype.showName=function(){
console.log(this.name);
}
var p=new Person("Sue");
//以上函数的原型链
p-->Person.prototype-->Object.prototype-->null
//其中,-->表示__proto__,该原型链的含义为:p.__proto__指向其构造函数的原型Person.prototype,而构造函数的原型默认是Object的实例,因而指向Object.prototype,之后便结束为空,至于原理,请参照上篇web前端开发中关于面向对象(二)进行理解。
通过几次的实验和验证可以发现,原型链的末端都是Object.prototype,所有的对象都直接或间接的继承Object。
二、精确判断数据类型的方法
判断数据类型不仅仅有typeof一种方式,且typeof方法只能判断一般的数据类型,得到的结果较为模糊,不够精准。若想要精确的判断数据类型,需用到Object.prototype上的toString方法。使用Object.prototype.toString方法判断数据类型需要改变this的指向,因而需要配合call一起使用。typeof与Object.prototype.toString的区别:
var arr= [];
var fn = function(){};
var str = "str";
var obj = {};
var date = new Date();
var regExp = new RegExp();
console.log("toString判断结果:"+Object.prototype.toString.call(arr));
console.log("typeof判断结果:"+typeof arr);
console.log('------------------')
console.log("toString判断结果:"+Object.prototype.toString.call(fn));
console.log("typeof判断结果:"+typeof fn);
console.log('------------------')
console.log("toString判断结果:"+Object.prototype.toString.call(str));
console.log("typeof判断结果:"+typeof str);
console.log('------------------')
console.log("toString判断结果:"+Object.prototype.toString.call(obj));
console.log("typeof判断结果:"+typeof obj);
console.log('------------------')
console.log("toString判断结果:"+Object.prototype.toString.call(date));
console.log("typeof判断结果:"+typeof date);
console.log('------------------')
console.log("toString判断结果:"+Object.prototype.toString.call(regExp));
console.log("typeof判断结果:"+typeof regExp);
/*结果:
toString判断结果:[object Array]
typeof判断结果:object
------------------
toString判断结果:[object Function]
typeof判断结果:function
------------------
toString判断结果:[object String]
typeof判断结果:string
------------------
toString判断结果:[object Object]
typeof判断结果:object
------------------
toString判断结果:[object Date]
typeof判断结果:object
------------------
toString判断结果:[object RegExp]
typeof判断结果:object
*/
三、函数和Function的关系
函数,在上篇已经提到过,在不同的情况下所担任的角色就各不相同,可作为构造函数、普通函数等。而普通函数在使用字面量声明时就是构造函数,var fn=new Function(){}
有时候也可是构造函数。那么,函数和Function两者之间又有何关联?仅仅使用文字是解释不太明白的,还是让代码来告诉大家。
function foo(){...}
console.log(foo.__prototype==Function.prototype);//true
console.log(Object.__proto__==Function.prototype);//true
console.log(Function.__proto__==Function.prototype);//true
/*原型链:
foo-->Function.prototype-->Object.prototype-->null
object-->Function.prototype-->Object.prototype-->null
Function-->Function.prototype
*/
四、关于defineProperty
Object中具有一个方法:defineProperty,该方法是定义特性。该方法具有三个参数。
(1)第一个参数:目标对象
(2)第二个参数:需要定义属性和方法的名字
(3)第三个参数:目标属性所拥有的特性,必须是对象
例:
var a={};
Object.defineProperty(a,"b",{
value:1,
writable:true,
configurable:true,
enumerable:true
});
以上例子的意思是:为对象a定义了一个属性,该属性为b,且为b赋值为1。defineProperty中第三个参数,对象中各个属性的含义:
(1)value:属性的值
(2)writable:属性值是否可以被重写,默认为false,若为true,则可以被重写
(3)configurable:总开关,默认为false。总开关一旦为false,value、writeable、configurable就不可被重设
(4)enumerable:是否可以在for/in中循环遍历,默认为false
defineProperty方法的第三个参数对象中还有两个属性,get和set,若使用get和set者两个属性,则就不需要再同时使用以上四个属性。
(1)get:获取值
(2)set:赋值
例:
Object.defineProperty(a,"b",{
set:function(val){},
get:function(){}
});
我们可以通过defineProperty中的set和get者两个属性来实现数据的双向绑定。
例:
<input type="text" v-model="val"/>
<p v-model="val"></p>
<script> var inp=document.getElementsByTagName("input")[0]; var p=document.getElementsByTagName("p")[0]; var obj={}; Object.defineProperty(obj,"val",{ get:function() return inp.value; }, set:function(val){ p.innerHtml=val; inp.value=val; } }); inp.oninput=function(){ obj.val=inp.value; } </script>
五、实例判断
如何判断对象是否是构造函数的实例?instanceof:判断对象是否是构造函数的实例。如果构造函数的原型在原型链上,那么原型链开始的那个实例是所有原型所属的构造函数的实例。
例:
function Person(name){
this.name=name;
}
Person.prototype.showName=function(){
console.log(this.name);
}
function Student(num){
this.num=num;
}
Student.prototy=new Person("Sue");
var stu=new Student(90);
console.log(stu instanceof Student);//true
console.log(stu instanceof Person);//true
console.log(stu instanceof Object);//true
console.log(stu instanceof Function);//false
//原型链:
//stu-->Student.prototype-->Person.prototype-->Object.prototype-->null
六、属性判断
(1)hasOwnProperty():判断对象中是否有某个属性,该方法只能判断实例对象上的属性,不能判断原型对象上的属性。
(2)in:既可以判断实例对象上的属性,又可以判断原型对象上的属性
例:
var obj=new Object();
obj.info="Sue";
obj.name="Mike";
Object.prototype.age="20";
console.log(obj.hasOwnProperty("info"));//true
console.log("info" in obj);//true
console.log(obj.hasOwnProperty("name"));//true
console.log("name in obj);//true console.log(obj.hasOwnProperty("age"));//false console.log("age" in obj);//true
判断某个属性是否在原型上的方法:
if("info" in obj &&!obj.hasOwnProperty("info")){...}
七、原型继承
在其他语言中,面向对象中也存在继承和封装。在前端开发中面向对象也存在着继承,前端将继承分为几种,今天先看一看原型继承。其实,在以上提到的知识中已经涉及到也使用到了原型继承。
例:
function Animal(name){
this.name=name;
}
Animal.prototype.showName={
console.log(this.name);
}
function Tiger(age){
this.age=age;
}
var tiger=new Tiger("12");
以上代码块执行的结果只会输出12。老虎是动物的一种,按照常理而言,老虎应该具有动物的特性,那么执行以上代码块,老虎应该具有姓名和年龄两个属性才是合理的,而如今只输出了年龄,那么怎样才可以在不再Tiger构造函数中添加属性而获得Animal构造函数中的name属性?这里便涉及到–继承的知识点。若想要继承Animal,则代码块为:
function Animal(name){
this.name=name;
}
Animal.prototype.showName={
console.log(this.name);
}
function Tiger(age){
this.age=age;
}
Tiger.prototype=new Animal("caser");
var tiger=new Tiger("12");
如此,老虎便可继承Animal中的属性和方法,且它自身的方法和属性也不会被共享。这便是原型继承。但原型继承存在几个问题:
(1)无法在不影响所有实例的情况下,给要继承的构造函数传参
(2)继承的构造函数中的引用类型对于所有的实例是共享的
那么,又该如何解决原型继承存在的这些问题呢?请阅读下一篇。