javascript基础-原型链与继承

我理解的继承,是为了解决代码重复浪费空间与编写精力的问题,若有两个对象,面试

// person1
var person1={
    name:'tom',
    say(){
        console.log(this.name);
    }
}

// person2
var person2={
    name:'jerry',
    say(){
        console.log(this.name);
    }
}
复制代码

这两个对象都有相同的name属性和say()方法,只是name属性值不一样,形成了代码的重复浪费,所以提出了节省代码的方式:数组

工程模式

function person(name){
    var obj=new Object();
    obj.name=name;
    obj.say=function(){
        console.log(obj.name);
    };
    return obj;
}

var person1=person("tom");
var person2=person("jerry");
复制代码

构造函数

function Person(name){
    this.name=name;
    this.say=function(){
        console.log(this.name);
    };
}
var person1=new Person("tom");
var person2=new Person("jerry");
复制代码

其实两种方式大同小异,由于在使用new操做符来建立一个实例对象时,产生了如下4个步骤:bash

  1. 新建一个对象:var obj=new Object();
  2. 将构造函数中的this指向该对象,对该对象进行赋值obj.name='name
  3. 将该对象的__proto__指向构造函数的原型obj.__proto__=Person.prototype
  4. 返回该对象
    其中一、二、4步就是工程模式中的步骤,只是多了第3步。

这两种产生对象的方法,虽然节省了代码的书写量,但在内存上仍然消耗相同的空间,每建立一个新的实例对象仍然要建立新的属性和方法。因此就有了原型。函数

原型

(1)首先,js里全部的函数都有一个prototype属性,该属性是一个对象;同时js里面全部的对象(除去基本类型number,string,boolean,null和undefined以外的全部)都有一个__proto__属性,因此一个函数有prototype__proto__两个属性,能够经过console.dir(fn)查看。ui

function Person(name){
    this.name=name;
}
console.dir(Person);
复制代码

(2) prototype里有个构造器 constructor,指向的就是该构造函数,全部的对象都是由构造函数实例化获得的,如今咱们来看一下刚才讲new操做符时的第3个步骤:

  1. 将该对象的__proto__指向构造函数的原型obj.__proto__=Person.prototype 用图来表示就是:

由于全部的实例对象的 __proto__属性都指向构造函数的 prototype对象,因此能够把共享的方法写在 prototype里,这样只须要建立一个方法就能够了。

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log(this.name);
}

var p1=new Person("tom");
p1.say();   // tom
var p1=new Person("jerry");
p1.say();   // jerry
复制代码

原型链

有了原型的概念,先给出原型链的概念:实例对象在使用属性或者调用方法时,若是本身没有,则会往上一级级查找prototype对象,直到找到为止,若是最终也找不到则报错,就拿上面讲的,p1本身没有say方法,可是原型对象里面有该方法,因此能够调用。this

原型链继承

有了原型链的概念,咱们就能够实现继承了,即让子类构造函数的prototype指向父类的一个实例对象。这样经过原型链的查找就能够继承到父类的方法,咱们一般须要继承的都是方法。spa

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log(this.name);
}
// 子类构造函数
function Student(name){
    this.name=name;
}
// 将子类添加到原型链中
Student.prototype=new Person("tom");
// 子类本身的原型方法必须在改变原型指向后添加
Student.prototype.play=function(){
    console.log(this.name+" play");
}

var s1=new Student("jerry");
s1.say(); // 原型链上的方法 jerry
s1.play();  // 本身原型上的方法 jerry play 
// this一直指向都是s1,跟实例对象tom没有关系
复制代码

实例方法、静态方法、原型方法和内部方法

function Fn(){
    // 实例方法,只能经过实例对象.的形式调用
    this.work=function(){
        console.log("work");
    }
    // 内部方法 只能内部调用
    function learn(){}
};
// 静态方法,只能经过函数名.的形式调用
Fn.say=function(){
    console.log("say")
}
// 原型方法,只能实例.的形式调用
Fn.prototype.play=function(){
    console.log("play")
}
Fn.say();  // say
Fn.play(); // 报错
Fn.work(); // 报错
var f1=new Fn();
f1.say(); // 报错
f1.play(); // play
f1.work(); // work
复制代码

咱们可使用console.dir(Person)查看一下:prototype

能够看到静态方法和原型方法,实例方法与内部方法看不到。
其实这个问题是我在面试头条的时候暴露出来的,感谢面试小哥哥为我讲解,当时是有两个问题,怎么判断是数组,怎么让不是数组的元素调用splice方法,而后我就回答成了:

[1,2,3].isArray()
Array.splice.call(obj)
复制代码

完美搞错,真感谢那个面试小哥哥还耐心地给我讲解(捂脸羞愧)。
其实打印如下构造函数console,dir(Array)就能够看到3d

isArray是静态方法, splice是原型方法,因此正确的应该是:

Array.isArray([1,2,3]);
[].splice.call(obj); // []是Array的一个实例化对象
复制代码

instanceof操做符

l instanceof R 就是判断l的原型链上是否有R.prototypecode

s1 instanceof Student // true
s1 instanceof Person  // true
复制代码

缺点

父类原型上的引用属性会被子类们共享,一个子类更改了,其他的也会被更改;
子类实例没法向父类构造函数传参

构造函数继承

构造函数能够解决向父类构造函数传参的问题,但没有办法继承父类原型上的方法。

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log("say");
}

function Student(name,age){
    Person.call(this,name);
    this.age=age;
}

var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // 报错
复制代码

组合继承

即便用构造函数来继承属性,使用原型来继承原型方法

function Person(name){
    this.name=name;
}
Person.prototype.say=function(){
    console.log("say");
}

function Student(name,age){
    // 继承属性
    Person.call(this,name);
    this.age=age;
}
// 继承方法
Student.prototype=new Person();
var s1=new Student("xixi",12);
s1.name; // xixi
s1.age; // 12
s1.say(); // say
复制代码
相关文章
相关标签/搜索