原型和原型链的理解

JS是一门面向对象的编程语言,JS 中的面向对象,围绕原型和原型链知识展开。
原型是 JavaScript 面向对象系统实现的根基.javascript中创建对象不是基于‘类的’,而是通过构造函数和原型链实现的。
JS是基于原型链继承的。
在ES6中引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。ES6 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。

构造函数

function Student(name,number) {
   this.name = name
   this.number = number
}
var xialuo = new Student("夏洛",23)

new 构造函数背后做的事有:

  1. 创建一个空对象,把this指向空对象
  2. 将这个对象(this)的隐式原型(proto)指向构造函数.prototype(构造函数的原型对象)
  3. 执行构造函数.apply(this)
  4. return 这个对象

面试题
构造函数中返回一个对象对结果有什么影响???

function Super (a) {  
   this.a = a;  
   return 123;  
}  
Super.prototype.sayHello = function() {  
   alert('hello world');  
} 

function Super_ (a) {  
   this.a = a;  
   return {a: 2};  
}  
Super_.prototype.sayHello = function() {  
   alert('hello world');  
}  

const super1 = new Super(3)
const super2 = new Super_(3)
console.log(super1)    // Super { a: 3 }
console.log(super2) // { a: 2 }

在构造函数中 return 基本类型 不会影响构造函数的值, 而 return 对象类型 则会替代构造函数返回该对象。

function Foo () {
  getName = function () {
    console.log(1)
    return this
  }
}

Foo.prototype.getName = function () {
  console.log(3)
}

Foo.getName = function () {
  console.log(2)
}

var getName = function () {
  console.log(4)
}
function getName () {
  console.log(5)
}

getName()    // 4
Foo.getName()  // 2
new Foo().getName() // 3
// Foo().getName  // Cannot read property 'getName' of undefined

1. 理解原型和原型链

1.原型

每个构造函数都拥有一个 prototype 属性,它指向构造函数的原型对象,这个原型对象中有一个 construtor 属性指回构造函数;
每个实例对象都有一个__proto__属性,当我们使用构造函数去创建实例时,实例的__proto__属性就会指向对应构造函数的原型对象。

function Student(name,number) {
   this.name = name
   this.number = number
}
Student.prototype.sayHi = function () {
    console.log(`姓名:${this.name},学号:${this.number}`)
}
var xialuo = new Student("夏洛",23)
console.log(xialuo.name)     //夏洛
console.log(xialuo.number)   //23
xialuo.sayHi();  //姓名:夏洛,学号:23
原型图:

在这里插入图片描述
xialuo是通过Student new出来的,所以xialuo的__proto__指向Student的prototype
xialuo.proto === Student.prototype

查找规则

获取实例属性xialuo.name或执行实例方法xialuo.sayHi()时,先在自身属性和方法寻找,找不到则自动通过实例的__proto__到对应class的prototype(Student.prototype)中查找
ES6引入class写法:

class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
    }
    sayHi() {
        console.log(`姓名:${this.name},学号:${this.number}`)
    }
}
 const xialuo = new Student("夏洛",23)
 console.log(xialuo.name)     //夏洛
 console.log(xialuo.number)   //23
 xialuo.sayHi(); //姓名:夏洛,学号:23

 xialuo.hasOwnProperty("name")  //true
 xialuo.hasOwnProperty("sayHi")  //false

2.原型链

每个由构造器创建的对象,都有一个隐式引用(proto)链接到构造器的“prototype”属性值,即构造器的原型对象。原型对象可能有一个非空 (non-null) 隐式引用链接到它自己的原型,以此类推,这叫做 原型链 。
当试图访问一个 JavaScript 实例的属性 / 方法时,它首先搜索这个实例本身;当发现实例没有定义对应的属性 / 方法时,它会转而去搜索实例的原型对象;如果原型对象中也搜索不到,它就去搜索原型对象的原型对象,这个搜索的轨迹,就叫做原型链。

function People(name) {
  this.name = name;
}
People.prototype.eat = function () {
   console.log(`${this.name} eat something`)
}

//ES5寄生组合式实现继承
function Student(name,number) {
   // 先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
   People.call(this,name)            // 借用构造函数, 第一次调用父类构造函数
   this.number = number
}

Student.prototype = Object.create(People.prototype)  // 使用 Object.create 内置函数,通过明确指定原型来创建一个新对象。
Student.prototype.constructor = Student;

Student.prototype.sayHi = function () {
   console.log(`姓名:${this.name},学号:${this.number}`)
}

const xialuo = new Student("夏洛",23)
console.log(xialuo.name)     //夏洛
console.log(xialuo.number)   //23
xialuo.sayHi(); //姓名:夏洛,学号:23
原型链图

在这里插入图片描述
xialuo是通过Student new出来的,所以xialuo的__proto__指向Student的prototype。Student继承于People,内部原理为Student.prototype的原型指向People.prototype,所以Student.prototype.__proto__指向People.prototype
ES6的继承写法
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的属性添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。所以,在ES6中使用class实现继承,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

class People {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat something`)
    }
}
// 该类通过extends关键字,继承了 People 类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 People 类。
class Student extends People {
    constructor (name,number) {
        super(name)      // 调用父类的constructor(name)    
        // super作为函数调用时,代表父类的构造函数。
        // super()在这里相当于People.prototype.constructor.call(this)
        this.number = number
    }
    sayHi() {
        console.log(super.eat() + ',' + this.number)  // super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。// ES6规定通过super调用父类的方法时,super会绑定子类的this。
    }
}

const xialuo = new Student("夏洛",23)
console.log(xialuo.name)     //夏洛
console.log(xialuo.number)   //23
xialuo.sayHi(); //姓名:夏洛,学号:23

2. 判断继承关系的方法:

  1. A instanceof B的原理:通过A.__proto__的指向在A的原型链中向上寻找,看是否能找到B.prototype
    这种方法B必须是个构造函数
console.log(xialuo instanceof Student)     //true
console.log(xialuo instanceof People)    //true
console.log(xialuo instanceof Object)    //true

let arr = [1,2,3]
console.log(arr instanceof Array)   //true
  1. B.prototype isPrototypeOf(A):在A的原型链中向上寻找,看是否能找到B.prototype,这种方法不必访问构造函数。

  2. Object.getPrototypeOf(A) === B.prototype ;
    Object.getPrototypeOf(A):获取a的原型链,看是否能找到Foo.prototype

  3. A._proto _=== B.prototype

3.属性屏蔽

如果属性名foo即出现在实例对象A中,也出现在A的原型链中,那么会发生屏蔽。
实例对象A中的foo属性会屏蔽原型链上层的所有foo属性,因为A.foo总是会选择原型链中最底层的foo属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值