JS的继承
写在前面
①
prototype
:原型对象,专属于函数的,每个构造函数都有个prototype的方法,作用:为将来被创建出来的实例,做父级使用。
②__ proto __
:原型链,(隐示原型)标志了当前数据的父级数据,每个对象都具有一个名为proto的属性 指向他的构造函数的prototype
③constructor
:指向了当前所在的prototype所在的函数,在对象创建或者实例化时候被调用的方法
① 所有
引用类型
都有一个__proto__(隐式原型)
属性,属性值是一个普通的对象
② 所有函数
都有一个prototype(原型)
属性,属性值是一个普通的对象
③ 所有引用类型的__proto__
属性指向它构造函数的prototype
var a = [1,2,3];
a.__proto__ === Array.prototype; // true
什么是原型链?
链字面意思即代表一个查找的过程,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链
isPrototypeOf()
用于检测两个对象之间是否存在这种关系,使用方法如下:
Fn.prototype.isPrototypeOf(f) // 查看 Fn 的 prototype 对象,是否是 f 原型
类似的还有instanceof
运算符,使用方法如下:
console.log(f instanceof Fn) // 查看 f 对象是否是构造函数 Fn 的实例
console.log(f instanceof Object)
两种使用,如果是返回ture,如果不是返回false
请看如下代码:
function Person(){
}
Person.prototype.name = "Tom"
Person.prototype.say = function(){
alert('你好?')
}
var person = new Person()
console.log(person.name) //"Tom"
console.log(person.say)
//该函数的原型对象
console.log(Person.prototype) //返回一个对象,即原型对象,该对象包含一个name属性和一个say函数
//该实例
console.log(person.__proto__ == Person.prototype) //true
console.log(person)
console.log(Person.prototype.constructor)
这是上面构造函数的截图情况
综上,我们可以的到这样一些关系:
构造函数的继承
先来了解一下什么是构造函数吧,即在 JavaScript 中,用 new
关键字来调用的函数,称为构造函数。它的优点就是能够通过instanceof识别对象,缺点是每次实例化一个对象,都会把属性和方法复制一遍,那么下面就带大家来了解构造函数的继承以及如何解决构造函数的方法复制多次。
1.构造函数的继承
- 优点:简单方便易操作,可以实现多继承
+ 缺点:只能继承构造函数中的内容,不能继承原型上的内容
function A(){
this.a = 10;
}
function B(){
this.b = 20;
}
function Parent(n){
this.name = n;
this.say = function(){
console.log(this.name)
}
}
Parent.prototype.show = function(){
console.log("world")
}
function Child(n){
// Parent.bind(this,n)();
Parent.call(this,n);
A.call(this);
B.call(this);
// Parent.apply(this,[n]);
}
var p = new Parent("父母");
p.say();
p.show(); //world
var c = new Child("孩子");
c.say();
c.show(); //报错
console.log(c.a); //10
console.log(c.b); //20
补充说明:
bind,call,apply
的异同
同:
1.第一个参数都是this要指向的对象
2.都可以利用后续参数传参
3.都是用来改变函数的this对象的指向
异:
1.call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,所以后面还需要()来进行调用
2.call后面的参数与say方法中是一 一对应的,apply的第二个参数是一个数组,数组中的元素是和say方法中一 一对应的
如何解决构造函数的方法复制多次?
可以通过原型(prototype)对象,把方法写在构造函数的原型对象上
原型继承
1.原型对象—对象的拷贝
// 创建父对象
var Parent = {
name: 'wang',
age: 18,
friends: ['张三', '李四', '王五'],
showName: function(){
console.log(this.name);
}
}
// 创建需要继承的子对象
var Children = {};
// 开始拷贝属性(使用for...in...循环)
for( var i in Parent ){
Children[i] = Parent[i];
}
console.log(Children)
console.log(Parent)
缺点:如果继承过来的成员是引用类型的话,那么这个引用类型的成员在父对象和子对象之间是共享的,也就是说修改了之后, 父子对象都会受到影响.
- 原型对象继承:继承原型对象
- 优点:可以继承原型
- 缺点:不能继承构造函数
function Parent(n){
this.name = n;
}
Parent.prototype.say = function(){
console.log(this.name); //parent
}
function Child(n){}
for(var i in Parent.prototype){
Child.prototype[i] = Parent.prototype[i];
}
//更改子没有影响到父
Child.prototype.say = function(){
console.log("hello");
}
var p = new Parent("parent");
p.say(); //hello
console.log(p.name); //parent
//参数没有传递是原型继承的bug,也就是缺点
var c = new Child("child");
c.say();
console.log(c.name); //undefined
2.原型链
- 缺点:极其不方便传参
- 优点:结构清晰,每层原型上的内容互不相扰,保存多层信息
function Parent(n){
this.name = "admin";
}
Parent.prototype.say = function(){
console.log(this.name)
}
function Child(n){}
Child.prototype = new Parent();
Child.prototype.say = function(){
console.log("hello")
}
var p = new Parent();
p.say();
console.log(p)
var c = new Child();
c.say();
console.log(c)
缺点:不能给父构造函数传递参数,父子构造函数的原型对象之间有共享问题
混合继承
混合继承,即构造函数+原型继承
- 优点:构造函数继承—构造函数中的内容,原型继承—原型上的内容
- 缺点:复杂度上升
// 创建父构造函数
function Parent(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
// 设置父构造函数的原型对象
Parent.prototype.showName= function(){
console.log(this.name);
}
// 创建子构造函数
function Student(name){
Parent.call(this,name);
}
// 设置继承
Student.prototype = Parent.prototype;
Student.prototype.constructor = Student;
var p = new Parent("张三");
var s = new Student("李四");
p.showName();
s.showName();
console.log(p);
console.log(s)
解决了 父构造函数的属性继承到子构造函数的实例对象上,并且继承了父构造函数原型对象上的成员,解决了给父构造函数传递参数问题
class继承(ES6继承)
- 优点:简单方便易操作,既可以继承原型,又可以继承构造函数
- 缺点,兼容不好
class Parent{
constructor(n){
this.name = n;
}
say(){
console.log(this.name)
}
}
class Child extends Parent{
constructor(n){
super(n);
}
say(){
console.log("hello");
}
}
var p = new Parent("parent");
p.say();
console.log(p);
var c = new Child("child");
c.say();
console.log(c);