---- AI试用 ---域名问题某些图片和js资源无法访问,导致一些代码实例无法运行!(代码里gzui.net换成momen.vip即可)

JS基础-原型和原型链

前端开发 蚂蚁 1058℃ 0评论

https://mp.weixin.qq.com/s/cGXdxjpuk3eXPLdgr8HDrQ

1. 简述

原型链属于必考的知识点,有以下原因

  • js本身就是基于原型继承的语言
  • 在es6之前,我们只能通过写原型来继承,不像java可以通过class来继承
  • 在es6之后,我们也可以通过class来继承,但只是写法上相似,它真正的继承还是原型上的继承

2. 题目

  • 如何判断一个变量是不是数组
  • 手写一个简易的jquery,考虑插件和扩展性
  • class的原型本质,怎么理解

3. 知识点

3.1. class和继承

class本质上就是一个模板

  • constructor => 使用构造方法构建一些东西

  • 属性 => 这个模板有属性

  • 方法 => 这个模板有方法

  • 示例1

class Student {
  // 定义构建这个类的时候的事情 - 实例化的时候直接执行,且只执行一次
  constructor(name, number) {
    // this表示当前你正在构建的实例
    this.name = name
    this.number = number
    this.gender = 'male'
  }

  // 方法 - 动作
  sayHi() {
    console.log(`姓名${this.name}, 学号${this.number}`)
  }

  sutudy() {}
}

// 通过类 new 对象/实例
// new的时候他会走constructor(构建),把属性赋值给当前正在初始化的实例
const xialuo = new Student("夏洛", 100);
console.log(xialuo.name)
console.log(xialuo.number)

xialuo.sayHi();

示例2 - 继承

  • 当我们有很多个class,而且这些class有一些公共的属性的时候,可以将他们抽离出来
  • 使用extend继承
  • 使用super继承父类的构造函数
  • 扩展或者重写方法
// 父类
class People {
  constructor(name) {
    this.name = name;  // 每个人都有名字这个属性
  }
  eat() {
    console.log(`${this.name} eat something`);
  }
}

// 子类1
class Student extends People {
  // 即这个class继承了People的属性和方法
  constructor(name, number) {
    // 子类必须使用super方法将属性值传给父类,让父类统一作处理
    super(name);  // 我们把这个名字给父类(继承),让父类取处理这个属性

    this.number = number;  // 只有学生有学号
  }

  // student类自有的方法
  sayHi() {
    console.log(`姓名${this.name}, 学号${this.number}`)
  }
}

// 子类2
class Teacher extends People {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    teach() {
        console.log(`${this.name} 教授 ${this.major}`)
    }
}

// 学生实例
const xialuo = new Student("夏洛", 100);
console.log(xialuo.name)
console.log(xialuo.number)

xialuo.sayHi();
xialuo.eat();  // 继承自父类的方法

// 老师实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()  // 继承自父类的方法

3.2. 类型判断instanceof

  • 使用以上例子 => 可以使用instanceof判断这个对象属于哪个类
  • 即也可以说可以使用instanceof判断这个对象是否由哪个类构建出来的
xialuo instanceof Student  // true  student先继承于people,people再继承于object,所有object不是student的父类
xialuo instanceof People  // true  people也参与了该对象的构建
xialuo instanceof Object  // true  js中object是所有class的父类
  • 数据类型判断
[] instanceof Array  // true  判断[]是不是Array这个类构建出来的
[] instanceof Object  // true  // 实际上object也算是Array的父类
{} instanceof Object  // true

3.3. 原型和原型链

3.3.1. 原型

  • 示例
// class 实际上是函数,可见是语法糖
typeof People  // 'function'
typeof Student  // 'function'

// 隐式原型和显示原型
console.log(xialuo.__proto__)  // 隐式原型
console.log(Student.prototype)  // 显示原型 prototype就是原型的意思
// 全等意思是他们的引用了一个内存地址
console.log(xialuo.__proto__ === Student.prototype)
  • 注意事项:

我们在定义一个Student类的时候,会有一个显示原型指向一个对象,把方法都放到这个对象(原型)中来

  • xialuo这个对象实际上除了本身有name和number两个属性值之外,还有一个proto,所有的方法都是通过这个隐式原型指向Student类中的原型获取的

  • 被实例化出来的对象的proto和类的原型(方法)指向同一个内存地址

  • 原型关系

每个class都有显示原型prototype

  • 每个实例都有隐式原型proto

  • 实例的prototype(隐式原型)指向对应class的prototype(显示原型)

  • 基于原型的执行规则

获取属性xialuo.name或执行方法xialuo.sayhi()

  • 先在自身的属性和方法寻找

  • 如果找不到则自动去proto(隐式原型)中查找

  • 示例图图片

3.3.2. 原型链

  • 示例
console.log(Student.prototype.__proto__);
console.log(People.prototype);
// 父类的显示原型 === 子类的显示原型的隐式原型
console.log(People.prototype === Student.prototype.__proto__);
  • 注意事项

    • Student的隐式原型指向People的显示原型
    • People本身有显示原型,有eat方法
    • xialuo是被Student类new出来的,所以夏洛的隐式原型指向的是student的显示原型
    • student类里面有一个prototype对象,里面有个sayHi方法
    • student类可以理解为是people类new出来的(实际上不是,但可以这么理解),所以student的隐式原型,指向people的显示原型
  • 举个栗子

我们访问xialuo.name是直接获取xialuo这个实例本身的

  • 我们访问xialuo.sayHi(),xialuo这个对象本身没有sayHi属性,我们在隐式原型中去找,即Student类的原型(即student的prototype对象)中去找

  • 我们要访问xialuo.eat(), 我们一层层往上找,Student的原型中没有,于是在Student的隐式原型中去找,即People的原型(即people的prototype对象)中去找

  • 这个就是原型链

  • 示例图

    图片

  • 【扩展】如何判断某个属性是否是某个对象的属性

xialuo.hasOwnProperty('name')  // true
xialuo.hasOwnProperty('sayHi')  // false
  • hasOwnProperty是如何来的,我们可以做以下假设

hasOwnProperty也是xialuo身上的方法,但他也不是他自身的属性

  • 即示例图图片

  • People继承于object

所以people的隐式原型指向object这个原型,即object的prototype对象,而object的隐式原型proto为null

  • object这个原型是js引擎自己有的,他有toString()、hasOwnProperty()等方法
  • 所以xialuo.hasOwnProperty()方法是通过原型链一层层往上找,找到object,然后使用object的原型中的方法

3.3.3. 再看instanceof

  • instanceof的工作原理是怎么样的

  • 就按照 xialuo instanceof Object来说

    instanceof前面的变量(xialuo),顺着隐式原型,一层层往上找,找到student、people、object的显示原型

    • 如果这个隐式原型能对应到object的显示原型,那么xialuo instanceof Object成立

4. 重要提示

  • class是ES6语法规范,是由ECMA委员会发布的

  • ECMA只规定语法规范,即我们代码的书写规范,不规定如何实现

  • 以上实现方式都是V8引擎的实现方式,也是主流的

    • 拿V8引擎作为一个标准,作为一个实现方式的规范,来讲原型是完全没有问题的

5. 面试题解答(总结)

  • 如何判断一个变量是数组

    a instanceof Array

  • class的原型本质

先在自身的属性和方法寻找

  • 如果找不到则自动去proto(隐式原型)中查找
  • 原型
  • 原型链
  • 每个类都有一个显示原型
  • 每个实例都有一个隐式原型
  • 实例的隐式原型指向对应类的显示原型
  • 子类的显示原型的隐式原型指向父类的显示原型
  • 上文原型和原型链的图示
  • 主要有以下几点
  • 属性和方法的执行规则
  • 手写简易的jquerey(考虑插件和扩展性)

    • 手写demo传送门(见代码笔记)
// 只是一个简易的结构
// jquery主要是做dom查询的
class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector)
    const length = result.length

    // 对查出来的selector进行遍历,把每一个dom都放到this上去
    for(let i=0; i<length; i++) {
      this[i] = result[i]
    }

    this.length = length
    this.selector = selector
  }

  get(index) {
    retuen this[index]
  }

  each(fn) {
    for(let i=0; i<this.length; i++) {
      const elem = this[i]
      fn(elem)
    }
  }

  on(type, fn) {
    return this.each(elem => {
      elem.addEventListener(type, fn, false)
    })
  }

  // 扩展更多DOM api
}

// 考虑扩展性有2种方法
// 方法一:插件的形式
// 在jQuery类的显示原型上添加方法(实际上相当于在类中添加方法,因为类中的方法实际上就是体现在显示原型上的方法)
// 这种方法实际上还是jquery,只是在里面去做扩展
jQuery.prototype.dialog = function (info) {
  alert(info)
}

// 方法二:使用继承 即造轮子
// 这种方法是继承了jquery,但最后实现的类是自己的
class myJQuery extends jQuery {
  constructor(selector) {
      super(selector)
  }
  // 扩展自己的方法
  addClass(className) {

  }
  style(data) {

  }
}

// 打印出页面所有的p标签
const $p = new jQuery('p');
console.log($p);

console.log("获取第2个p标签", $p.get(1));

$p.on('click', () => alert('clicked'))
  • 原型链的坑
console.log(xialuo.__proto__.sayHi)  // 可以打印出方法

// 这种的话就是当作xialuo这个对象的隐式原型的方法执行的
// xialuo.__proto__.name这些值是没有的
xialuo.__proto__.sayHi()  // 执行的时候里面的值 undefind

xialuo.sayHi();  // sayHi是当作对象的方法执行的
// 那么this就是xialuo这个对象/实例,
// 对象的方法实际上都是在隐式原型上,
// 实际上 xialuo.sayHi() 有些类似 xialuo.__proto__.sayHi.call(xialuo)

// 执行隐式原型的方法,并把this指向实例
xialuo.__proto__.sayHi.call(xialuo)

代码笔记:

https://zmx2321.github.io/blog_code/interview/interview-one-side/5.html

转载请注明:有爱前端 » JS基础-原型和原型链

喜欢 (0)or分享 (0)