原型,原型链,call/apply
(一)原型
- 原型的概念解析
(1)概念:原型是function对象的一个属性①,它定义了构造函数制造出的对象的公共祖先②,通过该构造函数产生的对象,可以继承该原型的属性和方法③,原型也是对象
(2)解析:
①
//① Person是一个构造函数,也是function对象
Person.prototype // prototype是function对象的一个属性
function Person(){}
var person1 = new Person();
②
var person1 = new Person(); //构造函数制造出来的对象
var person2 = new Person(); //构造函数制造出来的对象
//② Person.prototype 是person1和person2的公共祖先
③
Person.prototype = {
name:'小张',
age:29,
sayName:function(){
console.log(this.name);
}
}
function Person(){}
var person1 = new Person();
var person2 = new Person();
console.log(person1.name); //小张
console.log(person2.sayName()); //小张
//③ person1和person2身上没有属性,它是继承了它原型上的属性和方法
(3)补充:
① 是函数它都有原型
② 原型在构造函数/函数出生时,系统就定义好了
③ 原型也是一个对象
④ 对象也是由构造函数构造出来的
- 原型的特点
(1) 如果对象/构造函数构造出的对象自己身上有这个属性和方法,就用自己的,如果没有就用原型的(祖先)(就近原则)
(2) 对象既可以有自己的属性和方法,也可以有原型为它提供的属性和方法
- 原型的应用
(1) 可以把共有的属性和方法放到原型上去,每一次创建对象时,就可以去他的祖先(原型)那里继承了
(2) 代码说明
|
Car.prototype = { //原型上放着car1和car2的共有属性和方法 carName:'Audi', height:1500, lang:3800, type:{ one:'A', two:'B' } } function Car(color,owner){ this.owner = owner; this.color = color; }
var car1 = new Car('red','小明'); var car2 = new Car('blue','晓东');
|
- 原型的增删改查
(1) 查:对象名.原型上的属性名 = ‘值’
(2) 增:构造函数名.prototype.要增加在原型上的属性名 = ‘值’
(3) 改:构造函数名.prototype.要修改的原型上的属性名 = ‘值’
(4) 删:delete 构造函数名.prototype.要删除的原型上的属性名
- 原型与对象上的一些属性
(1)constructor (构造器)
① 它是原型上一个自带的属性,返回构造对象的构造函数
② 使用方法:构造函数构造出的对象.constructor
③ 代码说明
|
function Demo(){
} var demo = new Demo(); console.log(demo.constructor); //ƒ Demo(){}
|
④ 对于不是构造函数构造出的对象的对象调用这个属性上会返回Object构造函数
|
var demo = {}; console.log(demo.constructor); //ƒ Object() { [native code] }
|
⑤ 不是构造函数构造出来的对象通过constructor属性可以看出其也是由构造函数构造而成的
|
var demo = {}; === function Object(){}; var demo = new Object(); |
(2)__proto__
① 对象会自带一个属性__proto__,这个属性指向其对象的原型
② 在前面用构造函数构造对象时,要有一个new,有new后会有三步,当时还用that显示的模拟了这三步,而显示的模拟我们并不建议那么用,原因就是这个__proto__属性

③ 上面的构造函数构造对象时的第一步会创建一个this对象,其实在这个this对象里面就有__proto__属性
var this = {
__proto__:该构造函数.prototype;
}
④ 上面说过对象如果去访问一个属性,这个属性如果自己有就用自己的,如果没有就用原型上的,那么访问原型上的这个属性的中间就是靠__proto__这个属性搭建的,具体实现过程见下面

⑤ 可以通过动态修改这个属性,让对象指向其他的原型
|
Car.prototype = { name:'car' } function Car(){
} Demo.prototype = { name:'demo' } function Demo(){
} var car = new Car(); var demo = new Demo(); console.log(car.__proto__);// {name: "car"} car.__proto__ = Demo.prototype; //动态修改car的原型指向 console.log(car.__proto__); //{name:"demo"}
|
⑥ 原型的对象问题展示
|
代码一 |
|
Person.prototype.name = '小猪'; //① function Person(){ //var this = { __proto__:Person.prototype } //这是一个空间,指向①处 } var person1 = new Person(); //这里通过构造函数构造对象,这里的原型在①处
//下面会新建一个空间,让其指向②处 Person.prototype = { //② name:'小龙' //这是一个新的原型对象,和①没有任何关系 这是一个新建的空间 } // __proto__:Person.prototype //让__proto__这个属性指向②处新建的空间 Person.prototype.name = '小花'; //③ 这是在②对象的属性上改变属性值 var person2 = new Person(); console.log(person1.name); //小猪 console.log(person2.name); //小花
|
|
代码二 |
|
Person.prototype.name = '小猪'; //① function Person(){} var person1 = new Person(); //这里通过构造函数构造对象,这里的原型在①处 Person.prototype.name = '小花'; //③ 这是在①对象的属性上改变属性值 Person.prototype = { //② name:'小龙' //这是一个新的原型对象,和①没有任何关系 } console.log(person1.name); //小花
|
|
代码三 |
|
Person.prototype.name = '小猪'; //① function Person(){} Person.prototype = { //② name:'小龙' // 这是在构造对象之前改的原型,虽说是一个新对象,因为与①处的重名,会把①处的覆盖 } var person1 = new Person(); //这里通过构造函数构造对象,这里的原型在②处 console.log(person1.name); //小龙
|
- 其他补充
(1) 原型也是对象,数组也是对象,函数也是对象
(2) 在开发时,如果你的一个属性不想被别人用或者修改,可以在属性名前加一个下划线_,如_demo,表示私人的
(二)原型链
- 概念:把各个原型连在一起,就形成了原型链,连接点:__proto__
- 代码说明
|
Grand.prototype.lastName = '张'; function Grand(){ this.name = '国庆'; } var grand = new Grand(); Father.prototype = grand; function Father(){ this.name = '改革'; } var father = new Father(); Son.prototype = father; function Son(){ this.name = '小宝'; } var son = new Son(); son.lastName = '王'; console.log(son.lastName); //王 console.log(father.lastName); //张
|
- Object.prototype是所有对象的最终原型,它上面就没有__proto__
- 绝大多数(不是所有)对象最终都会继承自Object.prototype
- 这里的绝大多数对象而不是所有对象,是因为有一种创建对象的方法,可以创建出一种不带原型的对象,因为它没有原型,更别说最终原型了,这种方法是Object.create(原型/null)
- Object.create(原型/null)(创建对象的第三种方法)
(1) 创建方式
var obj = {
name:'小李'
}
var obj1 = Object.create(obj);
(2) 上面第5步说了这个方法可以创建一个没有原型的对象,创建方式就是Object.create(null),虽然它没有原型,但是我们可以手动跟他加原型,但是手动加的原型,系统识别不到,因此也无法访问到其原型上的属性,如

(3) __proto__属于系统内部属性,我们可以手动改动他的原型指向,他是绝对不能给一个没有原型的对象,通过它来添加原型
(4) 原型链的增删改查(和原型上的一样)

(5) 按理来说,子孙是不能直接修改祖先(原型)上的属性的,但是原型上如果有一个引用值,子孙可以通过调用其引用值修改其引用值中的东西的,具体代码如下
修改前

修改后

(6) 原型和this指向问题
下面代码中sayName方法里面的this指向谁呢?
谁调用的这个方法,this就指向谁
|
Person.prototype = { name:'小张', sayName:function(){ console.log(this.name); } } function Person(){ this.name = '小李'; } var person = new Person(); console.log(person.sayName()); //'小李'
//sayName里面的this指向是什么,谁调用的sayName的这个方法,this就指向谁
|
(7) toString()方法的深层原理
① 我们在讲解toString()方法时,说过undefined和null没有这个方法,而其他的数据类型,如数字,字符串,布尔值就有这个属性呢,原因是undefined和null没有包装类,而数字,字符串,布尔值是通过包装类访问到这个属性的
② 过程
原始值 ——> 包装类 ——> 把自己包装成对象 ——> 访问对象原型上的toString方法
(8) 为什么对象调用toString()返回"[object Object]"而数字或者其他原始值返回的是"数字或其他"呢
① 原因:因为对象调用的这个toString()是终极原型上的,而其他原始值调用的这个toString()是其包装类原型上的
② 我们可以通过call来改变原始值的调用指向,让它调用终极原型上的toString()

- 补充问题
(1) 不要这样调用toString方法(直接数字.toString()),因为系统会把这个点解析为浮点数的点(因为其优先级高)
(2) 方法的重写:如果原型上有这个方法,我们自身也有这个方法,这就叫方法的重写(同样的名,不同的方法,是一种覆盖的方法)
(3) 终极原型下面的原型也有toString()方法,这就是系统自带的方法的重写(选用时采取就近原则)

(4) document.write()也是隐式的去调用了toString()方法(下面是验证document.write()是否会调用toString()方法)

(5) Js在处理小数数据时,精度是不准的,所以我们要避免小数操作,如果要使用小数,尽量使用向上取整(Math.ceil() 123.234 --> 124)/向下取整(Math.floor() 123.998 --> 123 )
下面代码是向上取整和向下取整的小demo(取0到100之间的整数)

(6) Math.random() 0到1之间的开区间的随机数
(7) toFixed(a) 小数点后保留a位
(8) Js可正常计算的范围:小数点前16位,后16位
(三)call/apply
- call/apply的作用是改变this指向,区别是传参列表不同
- call/apply(改变this指向的对象,参数)
- 只有函数才可以调用call/apply方法(函数本身就是一个方法)
- 可以把两个方法中重复的属性放在一个方法里面,用call来借用,具体代码如下:
|
function PersonOne(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } function PersonTwo(name,age,sex,color){ //var this = { __proto__:PersonTwo.prototype } PersonOne.call(this,name,age,sex); this.color = color; } var person1 = new PersonTwo('小李',23,'男','blue'); console.log(person1);
|
- call传参:需要把实参按照形参式的个数传进去
- apply传参:需要传一个实参列表(arguments)(数组)具体代码如下
|
function PersonOne(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } function PersonTwo(name,age,sex,color){ //var this = { __proto__:PersonTwo.prototype } PersonOne.apply(this,[name,age,sex]); this.color = color; } var person1 = new PersonTwo('小李',23,'男','blue'); console.log(person1);
|
- 函数执行的工作原理
function test() {}
test() --> test.call()
- 其他代码说明
|
call改变this指向 |
|
function Person(color){ //this == obj this.name = '李明'; this.age = 29; this.color = color; } var person1 = new Person(); var obj = {}; // Person.call(obj); //让Person里面预设的this全部变成obj Person.call(obj,'red'); //传参 console.log(obj); //{name: "李明", age: 29, color: "red"}
|
浙公网安备 33010602011771号