十一、原型与原型链深入、对象继承、call、apply
原型与原型链深入
前导知识
几乎所有对象都有自己的原型,包括原型对象也有自己的原型。
例外:
var obj = Object.create(null);
console.log(obj);
原型
Prototype
构造函数的原型对象是实例化对象的公共祖先,实例化的不同对象能继承原型中的属性和方法。
构造函数的prototype可以手动设置吗? √
Professor.prototype.tSkill = 'Java';
function Professor(){}
var professor = new Professor();
Teacher.prototype = professor;
function Teacher(){
this.mkill = 'JS/JQ';
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
this.pkill = 'HTML/CSS';
}
var student = new Student();
console.log(student);
console.log(student.pkill, student.mkill, student.tSkill);
//HTML/CSS JS/JQ Java
当我们在student对象上寻找属性时,找不到就去它的构造函数的原型对象上teacher上寻找,若找不到,继续向teacher对象的构造函数的原型对象professor上找,以此类推
原型链
对象沿着__proto__在原型上寻找属性就形成了一个链条式的继承关系,这个继承关系,我们就叫做原型链。
原型链有顶端吗? -> Object.prototype
console.log(Professor.prototype);
Object.prototype
下保存了一个toString
方法。
实例化对象只能查看原型链上的prototype吗?×
function Teacher(){
this.mkill = 'JS/JQ';
this.success = {
alibaba: '28',
tencent: '30',
jingdong: '11'
}
this.students = 500;
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
this.pkill = 'HTML/CSS';
}
var student = new Student();
当原型的某个属性值是引用值类型:
- 实例化对象:学生
- 原型对象:老师
不推荐这样使用
通过学生增加老师的属性可以吗?√ 学生的属性会增加吗?×
通过学生更改老师的属性可以吗?√ 学生的属性会增加吗?×
通过学生删除老师的属性可以吗?√
student.success.baidu = '100';
student.success.alibaba = '29';
delete student.success.jindong;
console.log(teacher, student);
当原型的某个属性是原始值类型:
通过学生增加老师的属性可以吗?× 学生的属性会增加吗?√
student.students++;
console.log(teacher, student);
这个过程相当于:
student.students = student.students + 1;
student.students在原型链中可以访问到,值为500,+1为501;
然后在实例化对象中增加一个student属性,属性名为students,赋值为501;
原型对象方法上的this指向谁?
谁使用this,this就指向谁
function Car(){
this.brand = 'Benz';
}
Car.prototype = {
brand: 'Mazda',
intro: function(){
console.log('我是'+ this.brand + '车');
}
}
var car = new Car();
car.intro(); // 我是Benz车
Car.prototype.intro(); //我是Mazda车
实例化对象的this指向自己,car.intro()
只是借用原型上的方法,当this指向的对象中有这个brand属性时,直接调用;当对象中没有这个属性,通过this指向的对象的__proto__来访问原型对象看原型上有没有这个属性。
如果想打印原型上的属性,就让原型对象去调用自己的方法,this指向原型对象。
对象继承
生成对象的方法
- 构造器是Object() ->它的原型对象是Object.prototype
- 字面量方法
var obj = {}
- 系统自带的构造函数
new Object()
(公司不用这个)
- 字面量方法
- 构造器是自己:自定义构造函数
- object.create(对象/null)
对于自定义构造函数,它的原型对象的原型是由系统自带的Object构造出来的。
Object.create(对象/null)
object.create创建新对象,并指定它的原型对象
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的原型对象。
即 创造一个新对象,并且指定这个对象的原型对象,创建一种继承关系。
function Car(){}
Car.prototype.num = 1;
var car1 = new Car();
var car2 = Object.create(Car.prototype);
console.log(car1);
console.log(car2);
Object.create(原型对象)创造的对象与通过构造函数实例化对象,他们生成对象的内容是一致的。
new
做了什么:
- 实例化对象
- 调用构造函数的初始化属性和方法
- 指定实例对象的原型
object.create()
做了什么:
- 创建一个新对象
- 把别的对象作为创建的对象的原型,从而实现继承
所有的对象都继承Object.prototype?×
原型链的顶端是:Object.prototype
是不是所有对象都继承Object.prototype?×
Object.create(null)创建的对象什么属性都没有!
var obj = Object.create(null);
console.log(obj);
那能给这个空对象手动指定原型吗?√
那它能访问(继承)手动指定的原型吗?×
var obj = Object.create(null);
var obj1 = {
count: 2
}
obj.__proto__ = obj1;
console.log(obj.count); //undefined
只有系统指定的原型才能被继承
Object.prototype.toString()方法
先看一种情况:
不是说原始值没有属性吗?怎么还能调用方法?
var num = 1;
console.log(num.toString()); // '1'
console.log(undefined.toString()); //报错
console.log(null.toString()); //报错
对于原始值Number,String,Boolean类型时,在它们调用自身的方法和属性时,系统会把它们包装一下,变成对象。而undefined和null不能经过包装类变成对象。
通过系统内置的构造函数,new构造函数生成一个对象:new Number()
相当于是 new Number(num).toString()
这个toString()是从哪里来的?
打印一下数字对象:
console.log(new Number());
这个toString方法来自Number.prototype上的方法
那Object.prototype上有toString方法,为什么你数字对象的原型还要有toString方法?
方法是有区别的,在哪?处理的结果是不同的!!!
Object.prototype.toString.call(1);
打印对象类型的Number构造函数
Number.prototype.toString.call(1);
打印’1’
因为原型链顶端的Object.prototype上实现不了我要的功能,于是在构造函数的prototype上重写toString方法。
document.write()
document.write()
隐式转换为String类型
我实验出的:
- 原始值就直接转成字符串
- 引用值去找一下它有没有toString方法
var obj = {}
var obj2 = Object.create(null);
document.write(obj); //[object Object]
document.write(obj2);//报错:Cannot convert object to primitive value
页面上显示字面量空对象,会自动调用它所继承的Object.prototype上的toString方法,把其变成String类型。
而Object.create(null)空对象会报错?因为空对象中什么属性都没有,不会继承Object.prototype,更没有toString方法。
那能手动添加toString方法吗?√
var num = 1;
var obj = {}
var obj2 = Object.create(null);
obj2.toString = function(){
return 'obj2';
}
document.write(1);
document.write(obj)
document.write(obj2.toString());
document.write(obj2);
Call() apply()
Call(对象,参数列表)
函数执行时,其实隐式使用call方法();
function test(){
console.log(1);
}
test(); // 1
test.call(); // 1
它有什么用?
实现继承,一个对象中拿到构造函数所有属性和方法
function Car(brand, color) {
this.brand = brand;
this.color = color;
}
var newCar = {};
Car.call(newCar, 'benz', 'black');
console.log(newCar); //{brand: 'benz', color: 'black'}
函数使用call方法指定对象和参数后,改变了this的指向,变成了newCar.brand,newCar.color,对象相当于拿到了构造函数的方法和属性。
apply(对象,参数数组)
call()
方法的语法和作用与 apply()
方法类似,只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
开发中我们怎么用call和apply:
-
在一个对象中拿到其他构造函数中的方法
形式:在一个构造函数A中,调用另一个构造函数.call(this, 参数列表),然后实例化A,拿到另一个构造函数的属性和方法
不是在构造函数的方法中实例化另一个对象
function Car(opt){ this.displacement = opt.displacement; this.color = opt.color; this.brand = opt.brand; } function Person(opt){ //首行调用 Car.call(this, opt.carOpt); this.name = opt.name; this.age = opt.age; this.con = function(){ console.log('年龄为' +this.age + '岁姓名为' + this.name + '买了一辆排量为' + this.displacement + '的' + this.color +'的' + this.brand + '车'); } } var person = new Person({ name: '王五', age: 22, carOpt:{ displacement: '2.0T', color: '红色', brand: 'Benz' } }) console.log(person); person.con();
-
工作中多个人协同作业,最后合并成一个功能…
函数返回值
- 普通函数默认返回undefined:
return undefined
- 构造函数被实例化以后默认返回this:
return this