js深入理解原型、原型链、继承

导读:本篇文章讲解 js深入理解原型、原型链、继承,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、函数 函数原型 函数实例

函数是function关键字声明的函数,函数原型有个原型链的概念,每个构造函数都是保存在原型中prototype,prototype是函数的原型,原型中有个constructor,函数前面加new 关键字,就是函数的实例化,生成的就是函数实例。

1.函数

函数是function关键字声明的函数,也就是fun函数本身,也叫做构造函数。在创建函数fun的时候,也会自动为它创建一个prototype属性,这个属性的作用就是用来指向函数原型(原型对象)。prototype可以理解为fun函数的一个属性,保存着原型函数的引用。

function fun(){
    
}
2.函数实例

函数前面加new就是函数的实例化,生成的就是函数实例。f就是通过new fun()得到的函数实例。f的内部会有一个包含函数原型的指针[[prototype]],这时候f可以调用函数原型的属性和方法。但是[[prototype]]是内部属性,无法直接访问。

var f  = new fun();

那要什么解决这个问题呢?当然有方法可以访问内部属性。以下提供了两种方法

//_proto_:部分浏览器提供了此属性去访问[[prototype]]属性的值
//通过Object.getPrototypeOf去获取
3函数原型对象

函数原型对象有一个constructor的属性,这个属性指向包含一个指向prototype属性的所在函数的指针(f函数)

attention:

  • 每一个构造函数都有一个原型对象prototype,原型对象上包含着构造函数的指针constructor,而函数实例都包含着一个指向原型对象的的内部指针_ proto _。实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数。

  • 对象没有prototype属性,只有方法才有prototype。

  • 任何对象都有一个原型对象,任何对象都有一个constructor属性,指向创建此对象的构造函数,{}对象,它的构造函数是function Object(){};

在这里插入图片描述

二、class继承

  • ES6之前
function Student(name){
    this.name = name;
}

//给student新增一个方法
Student.prototype.hello = function(){
    alter('hello');
}
  • ES6引入class关键字,直接定义一个类(属性、方法)
class Student{
    constructor(name){
        this.name = name;
    }
    hello(){
        alter('hello');
    }
}

三、原型链

引用对象属性:首先在对象(instance)的内部寻找该属性,找不到再去该对象的原型(instance.prototype)里去找这个属性

如果让原型对象指向另一个类型的实例 constructor1.prototype = instance2

试图引用constructor1构造的实例instance1的某个属性p1:

(1)在instance内部属性寻找一次

(2)在instance1.proto(construct1.prototype)中找一遍,但是constructor1.prototype = instance2,也就是说在instance2中找属性p1

(3)如果在instance2中没有找到,它会继续在instance2.proto(constructor2.prototype)中寻找…直至Object的原型对象

因为搜索的轨迹像一条长链:搜索轨迹: instance1–> instance2 –> constructor2.prototype…–>Object.prototype

于是把这种实例与原型的链条叫做原型链

//instance实例通过原型链找到了Father原型中的getFatherValue方法
function Father(){
    this.property = true;
}

father.prototype.getFatherValue = function(){
    return this.property;	
}

function Son(){
    this.sonProperty = false;
}

//继承Father
Son.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写
Son.prototype.getSonValue = function(){
    return this.sonProperty;
}
var instance = new Son();//instance.constructor指向的是Father,因为Son.prototype中的constructor被重写。
alert(instance.getFatherValue());//true

在这里插入图片描述

  • 对象分为普通对象和函数对象,Function、Object是js自带的对象。通过new Function()创建的都是函数对象,其他都是普通对象。普通对象没有prototype,但有__proto__属性。

  • 确定原型和实例的关系

(1)通过操作符 instanceof

在这里插入图片描述

测试结果表明,instance是Object Father Son 中任何一个类型的实例,所以三个构造函数都返回了true

(2)isPrototypeOf()方法,只要原型链中出现过的原型,isPrototypeOf()就会返回true

在这里插入图片描述

  • 原型链的问题

(1)当原型链中包含引用类型值的原型时,该引用类型值会被所有实例所共享。

(2)在创建子类型(Son)的时候,不能向超类型(Father)的构造函数传递参数

怎么解决问题??继承!!!

四、继承

1.借用构造函数(constructor stealing)也叫做经典继承

基本思想:在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()apply()方法

<script>

function Father(){
    this.colors = ["red","blue","green"]; 
}

function Son(){
    Father.call(this);//继承Father,且向父类传递参数
}
//引用类型值独立
var instance1 = new Son();
console.log(instance1.colors);

var instance2 = new Son();
console.log(instance2.colors);

</script>

在这里插入图片描述

优点:

(1)可以在子类构造函数中向父类传递参数

(2)父类的引用属性不会被所有实例共享,保证原型链中引用类型值的独立

缺点:

(1)方法都在构造函数中定义,函数复用不可用,超类型(Father)类中定义的方法对子类型而言也是不可见的。因此很少用

2.原型链继承

基本思路:将父类的实例作为子类的原型

<script>

function Father(){
    this.value = true;
    this.table = {
        name:"gaby",
        age: 21,
    };
}

Father.prototype.getTable = function(){
    console.log(this.table);
    console.log(this.value);
}

function Son(){};
Son.prototype = new Father();//父类的实例作为子类的原型

let Son1 = new Son();
Son1.table.gender = "man";
Son1.getTable();

let Son2 = new Son();
Son2.getTable();
Son2.value = false;

console.log(Son2.value);

</script>

在这里插入图片描述

优点:

(1)父类的方法可以复用

缺点:

(1)父类的属性会被所有的子类所共享,更改一个子类的引用属性,其他子类也会受影响。

(2)子类型实例不能给父类构造函数传参

3.组合继承(伪经典继承)

将原型链和构造函数的技术组合到一起

基本思路:使用原型链实现对原型方法和属性的继承,用构造函数实现对实例的继承

<script>

function Father(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}

Father.prototype.getName = function(){
    alert(this.name);
}
function Son(name,age){
    Father.call(this,name);//继承实例属性,1调用Father()
    this.age = age;
}
Son.prototype = new Father();//2调用Father()
Son.prototype.getAge = function(){
    alert(this.age);
}

var instance1 = new Son("gaby",20);
console.log(instance1.colors);
instance1.getName();
instance1.getAge();

var instance1 = new Son("prada",21);
console.log(instance1.colors);
instance1.getName;
instance1.getAge;


</script>

在这里插入图片描述

优点:

(1)js常用的继承模式,instanceof()和isPrototypeOf()也能用于识别基于组合继承创建的对象。

(2)父类的方法可以复用

(3)可以在instance构造函数中向Father构造函数传递参数

(4)父类构造函数中的引用属性不会被共享

缺点:

(1)调用两次父类,造成不必要的内耗

4.原型式继承

借助原型基于已有的对象创建新的对象,同时还不必因此创建自定义类型。对参数对象的一种浅复制

基本思路:在object()函数内部,先构建一个临时性的函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

<script>

function objectCopy(obj){
    function f(){};
    f.prototype = obj;
    return new f();
}

let person = {
    name:"gaby",
    age:20,
    friends:["mike","amy","linda"],
    setName:function(){
        console.log(this.name);
    }
}

let person1 = objectCopy(person);
person1.name = "xiaoming";
person1.friends.push("adam");
person1.setName();

let person2 = objectCopy(person);
person2.name = "xiaohong";
person2.friends.push("lily");
person2.setName();

console.log(person.friends);


</script>

在这里插入图片描述

优点:

(1)父类的方法可以复用

缺点:

(1)父类的引用会被子类所共享

(2)子类不能向父类传参

ES5新增object.create() 方法规范化了上面的原型式继承.

object.create() 接受两个参数,一是用作新对象原型的对象,二是(可选的)一个为新对象定义额外属性的对象

5.寄生式继承

基本思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力。

<script>

function objectCopy(obj){
    function f(){};
    f.prototype = obj;
    return new f();
}

function createPerson(original){
    let clone = objectCopy(original);//通过调用objectCopy函数创建一个新对象
    clone.getName = function(){
        console.log(this.name);//以某种方式增强新对象
    }
    return clone;
}
let person = {
    name:"gaby",
    friends:["lily","tom","mike"]
}

let person1 = createPerson(person);
person1.friends.push("brone");
console.log(person1.friends);
person1.getName();

let person2 = createPerson(person);
console.log(person2.friends);


</script>

在这里插入图片描述

缺点:函数不能复用

6.寄生式组合继承

组合继承缺点:无论什么情况都会调用两次父类构造函数,一次是在创建子类原型的时候,另一次是在子类构造函数内部

寄生式组合继承出现就是为了解决这个问题,减低调用父类构造函数。

基本思路:不必为了指定子类型函数的原型而去调用超类型的构造函数

<script>

function objectCopy(obj){
    function f(){};
    f.prototype = obj;
    return new f();
}

function inheritPrototype(Son,Father){
    let prototype = objectCopy(Father.prototype);//创建对象
    prototype.constructor = Son;//增强对象
    Son.prototype = prototype;//赋值对象
}

function Father(name){
    this.name = name;
    this.friends = ["lily","tom","mike"]
}

Father.prototype.sayName  = function(){
    console.log(this,name);
} 

inheritPrototype(Son,Father);
Son.prototype.sayAge = function(){
    console.log(this.age);
}

let Son1 = new Son("gaby",20);
Son1.sayName();
Son1.sayAge();
Son1.friends.push("brone");
console.log(Son1.friends);

let Son2 = new Son("gaby",20);
Son2.sayName();
Son2.sayAge();
Son2.friends.push("brone");
console.log(Son2.friends);


</script>

在这里插入图片描述

五、碎碎念

这一部分的内容是前端JavaScript面试高频考点,要好好理解一下,向下扎根。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16168.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!