原型
一.原型定义
原型是function对象的一个属性。它定义了构造函数制造出的对象的公共祖先。
通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
(注:构造函数的书写规范需要满足大驼峰式,而其它函数没有这样的书写规范。)
二.原型用法
原型要如何使用呢?先来看这样一个小例子。
这是只使用构造函数创建对象的例子。
function Car(owner,color){
this.owner=owner;
this.color=color;
this.carName="BMW";
this.lang=4900;
this.height=1400;
}
var car=new Car("xiaoLiu","red");
上面的这个构造函数这三条语句 this.carName=“BMW”;this.lang=4900; this.height=1400;是每次构造时都要使用的。可不可以有什么方法能避免这样的偶合性,让这些重复的操作一次性执行好呢?
解决方法
我们可以将这三条属性设置为原型的属性。这样对象便可以继承这三个属性。
Car.prototype.carName="BMW";
Car.prototype.lang=4900;
Car.prototype.height=1400;
function Car(owner,color){
this.owner=owner;
this.color=color;
}
var car=new Car("xiaoLiu","red");
更便捷的解决方法
将原型当作一个对象来处理
Car.prototype={
carName:"BMW",
lang:4900,
height:1400
}
function Car(owner,color){
this.owner=owner;
this.color=color;
}
var car=new Car("xiaoLiu","red");
三.原型的增删改查:
以上述例子为例,通过构造函数构造出的对象在控制台上对原型进行增删改查。
(1)查:
car.lang
//结果为4900
注:如果这个属性既在构造函数中存在,又在原型中存在,对象会先在自己的属性中查找(也就是构造函数中的属性),当自己没有这个属性时,才会在原型中寻找。
(2)删
delete car.lang
//结果为ture
(再次查找)car.lang
//结果为4900
也就是说通过删除构造函数产生的对象的属性来删除原型上的属性是不可行的。因为删除car对象中一个没有的属性,结果也是true。除非直接在原型上操作才可删除
delete Car.prototype.lang
(3).修改和增添
直接在实例上操作是不可行的,必须在原型上操作才能修改和增添原型上的属性。
四.两个重要的属性
在每个函数对象创建的时候,它都会产生一个prototype属性,这个属性是一个指针,它指向原型。
原型中也会产生一个constructor属性,该属性指向函数对象。
在产生了对象后,对象中也会有一个 __ proto__属性,指向原型。
对象如何查看对象的构造函数 ——>constructor
注:原型中即使不手动定义其它属性。系统也会隐性(浅粉色)的给原型中定义一个constructor属性,其值为构造函数的名字。对象继承了这一属性。原型中的这一属性的值还可以手动更改。
对象如何查找原型 ——>__ proto__
五.小练习
Person.prototype.name="sunny";
function Person(){
}
var person=new Person();
Person.prototype={
name:"cherry";
}
输入
person.name
结果是什么呢?
"sunny"
在说明理由之前,先来了解为什么
a. Person.prototype.name=“sunny”;
b. Person.prototype={name:“cherry”;}
这两种格式是完全不同的?
在对象产生时会在构造函数中先产生对对象的__proto__属性的赋值。
var this={
__proto__:Person.prototype;
}
也就是说 __ proto__与Person.prototype这时指向了同一个空间。在进行语句a时只不过是将Person.prototype指向的空间不变,只是增添了一个属性。但语句b却是给Person.prototype重新开辟了一段空间,再增添属性。
理由
上例是先产生了 __ proto__的指向,这时它与Person.prototype的指向相同。之后再改变了Person.prototype的指向,在新的空间里对属性进行赋值,这时 __ proto__指向的空间的属性值并不会改变。
修改
Person.prototype.name="sunny";
function Person(){
}
Person.prototype={
name:"cherry";
}
var person=new Person();
这时在控制台输入 person. name,结果便是 "cherry"了
原型链
(1)原型链的概念
理解了原型之后,先来再看一个例子。
Grand.prototype.LastName="Deng";
function Grand(){
}
var grand=new Grand();
Father.prototype=grand;
function Father(){
this.name="xiaoLi";
}
var father=new Father();
// father. __proto__ =grand;
Son.prototype=father;
function Son(){
this.habbit="reading";
}
var son=new Son();
在这个例子当中,有三级关系,
构造函数Son的原型是对象father,
而构造出对象father的构造函数Father的原型是对象grand,
进一步的,构造出对象grand的构造函数Grand的原型是Grand.prototype。
这样,我们要访问son.LastName仍然可以找到。
使用这种在原型上面再加原型,再加原型的方法把原型连成链,访问顺序也是依照链的顺序像作用域链一样依次访问的链叫做原型链。
注:Grand.prototype. __ proto__=object.prototype;
object.prototype便是原型链的终端。
注:等效的两个语句
构造函数.prototype=另一个对象;
构造函数产生的对象. __proto__ =另一个对象;
(2)一个易理解的图片
(3)原型链的增删改查
通过构造函数出构造的对象在控制台上对原型链进行增删改查,其原理与结果都和原型的增删改查类似。
这里有一个小技巧可以实现原型链的修改(覆盖式修改不可以,这是调用式的修改)
例如下面这个例子:
function Father(){
this.name="xiaoLi";
this.fortune={
num1:"visas"
};
}
var father=new Father();
Son.prototype=father;
function Son(){
this.habbit="reading";
}
var son=new Son();
想要修改fortune属性。在控制台上输入
son.fortune.num2="house"
//fortune后面还有"."实际上就相当于给这个方法再添属性。
// 下面这条语句就无法实现修改,只是相当于在son对象里添加了一条fortune属性。
//son.fortune="house"
(4)小练习
function Father(){
this.name="xiaoLi";
this.num=100;
}
var father=new Father();
Son.prototype=father;
function Son(){
this.habbit="reading";
}
var son=new Son();
问题:如果在控制台上打印
son.num++
father.num与son.num的结果会是什么?
结果
理由:son.num++可以理解为son.num=son.num+1;
也就是说初始的son.num值为father.num,在此基础上再进行加上1的操作,这样son.num的值发生了变化,但是father.num并不会。也就是说无法直接通过修改对象属性的值来修改原型属性的值。
- 一个小的知识点:
a.sayName()
sayName里面的this指向是,谁调用的这个方法,this就指向谁。
(5)两个更深层的知识点
a.创建对象还可以通过Object.create(原型);的方法
b.绝大多数对象最终都会继承自Object.prototype而并非所有对象最终都会继承自Object.prototype。
如何理解b呢?
举个反例:
var obj=Object.create(null);
控制台上也并不会报错。但是它也就没有原型。
而且在控制台上手动通过__ proto__属性给它添上原型时,也并不会让obj产生系统默认的__ proto__属性,也就是说系统不会识别手动添加的这个属性。
注:Object.create(原型),undefined与null没有toString()方法。
call()与apply()
(1).call()/apply()
本质:改变this指向
区别:传参列表不同
(call——>需要把实参按照形参的格式传进去
apply——>需要传一个arguments)
(2)用法
a.call()
注:在调用call方法时,把执行函数里面的this改成所传进去的对象。也就是说通过函数来进行了对对象的封装。
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
}
function Student(name,age,sex,tel,grade){
Person.call(this,name,age,sex);
//用上面的这一条语句替代了下面的三条语句。
// this.name=name;
// this.age=age;
// this.sex=sex;
this.tel=tel;
this.grade=grade;
}
var student=new Student("liMing",17,"male",123,2019);
在控制台中就可以看到:
b.apply()
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
}
function Student(name,age,sex,tel,grade){
Person.apply(this,[name,age,sex]);
this.tel=tel;
this.grade=grade;
}
var student=new Student("liMing",17,"male",123,2019);
对toString()方法的补充
i>
123.toString()会报错误
原因:系统不会优先识别为对象调用,数学计算的"."优先级最高,会把它当做浮点型。
这样会产生错误。
ii>
var num=123;
num.toString();
//系统会将它转换为new Number(num).toString;
// Number.prototype.toString=function(){
}
//Number.prototype.__proto__=Object.prototype;
原型上有方法, 手动定义了一个不同功能同一个名字的方法叫做重写。直接修改该原型的已有方法的功能叫重写,在继承该原型的另一个原型上修改也叫重写。系统会自己重写toString()方法。就像Object.prototype有toString()方法,Number.prototype上也有toString()方法,但这个方法是重写的,与Object.prototype的toString()方法返回值并不相同。num对象进行toString方法的调用时,会从它自己的内容找起,没有toString方法后再找它的原型Number.prototype,当它有toString方法,于是就是用它的toString方法。不再找Object.prototype.toString了。
注:
var num=123;
Document.write(num);
//它的实质是Document.write(num.toString());