本文目录
  1. this
  2. 原型
    1. 原型的概念
    2. 显式原型和隐式原型
    3. 原型链
    4. 原型链的属性问题
    5. instanceof与原型链
    6. 面试题
  3. 执行上下文
    1. 变量提升与函数提升
    2. 执行上下文
    3. 执行上下文栈
    4. 面试题
  4. 作用域和作用域链
    1. 全局作用域和函数作用域
    2. 作用域与执行上下文
    3. 作用域链
    4. 面试题1
    5. 面试题2
  5. 闭包
    1. 循环加事件监听问题
    2. 闭包的理解
    3. 常见的闭包
    4. 闭包的作用
    5. 闭包的生命周期
    6. 闭包的应用
    7. 闭包的优缺点
    8. 面试题1
    9. 面试题2
  6. 对象的创建方式
    1. Object构造函数模式
    2. 对象字面量
    3. 工厂模式
    4. 自定义构造函数模式
    5. 构造函数+原型的组合模式
  7. 继承方式
    1. 原型链继承
    2. 借用构造函数继承
    3. 组合继承
  8. 事件的轮询机制

分类: web前端 | 标签: js

js高级-笔记

发表于: 2022-06-21 21:20:28 | 字数统计: 5.9k | 阅读时长预计: 29分钟

b站视频教程 尚硅谷JavaScript高级教程

部分知识和尚硅谷js基础篇重复了,这里只摘选一些重要的不重复的知识整理笔记: this、原型与原型链、执行上下文、作用域和作用域链、闭包、对象的创建、继承

this

1. this是什么?
  * 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
  * 所有函数内部都有一个变量this
  * 它的值是调用函数的当前对象
2. 如何确定this的值?
  * test(): window
  * p.test(): p
  * new test(): 新创建的对象
  * p.call(obj): obj

【案例】

function Person(color) {
     // console.log(this)
     this.color = color;
     this.getColor = function () {
         // console.log(this)
         return this.color;
     };
     this.setColor = function (color) {
         // console.log(this)
         this.color = color;
     };
 }

Person("red"); //this是谁?  window

var p = new Person("yello"); //this是谁?  新创建的对象

p.getColor(); //this是谁? p

var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj

var test = p.setColor;
test(); //this是谁? window

function fun1() {
    function fun2() {
        console.log(this);
    }

    fun2(); //this是谁? window
}
fun1();

原型

原型的概念

1. 函数的prototype属性
  * 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  * 原型对象中有一个属性constructor, 它指向函数对象
2. 给原型对象添加属性(一般都是方法)
  * 作用: 函数的所有实例对象自动拥有原型中的属性(方法)

【案例】

// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {

}
console.log(Fun.prototype)  // 默认指向一个Object空对象(没有我们的属性)

// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)

//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
    console.log('test()')
}
var fun = new Fun()
fun.test()

显式原型和隐式原型

1. 每个函数function都有一个prototype,即显式原型(属性)
2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
3. 对象的隐式原型的值为其对应构造函数的显式原型的值
4. 内存结构(图)
5. 总结:
  * 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  * 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  * 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

【案例】

//定义构造函数
function Fn() {   // 内部语句: this.prototype = {}

}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
console.log(Fn.prototype)
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn()  // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function () {
    console.log('test()')
}
//通过实例调用原型的方法
fn.test()

【内存图】

image-20220620173026278

原型链

1. 原型链(图解)
  * 访问一个对象的属性时,
    * 先在自身属性中查找,找到返回
    * 如果没有, 再沿着__proto__这条链向上查找, 找到返回
    * 如果最终没找到, 返回undefined
  * 别名: 隐式原型链
  * 作用: 查找对象的属性(方法)
2. 构造函数/原型/实体对象的关系(图解)
3. 构造函数/原型/实体对象的关系2(图解)
4.注意
a)函数的显示原型(prototype)指向的对象默认是空Object实例对象(但Object不满足)
b)所有函数都是Function的实例(包含Function)
c)Object的原型对象是原型链尽头(因为 Object.prototype.__proto__ === null )

【案例】

// console.log(Object)
//console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Fn() {
    this.test1 = function () {
        console.log('test1()')
    }
}
console.log(Fn.prototype)
Fn.prototype.test2 = function () {
    console.log('test2()')
}

var fn = new Fn()

fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)
// fn.test3()


/*
  1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
   */
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
/*
  2. 所有函数都是Function的实例(包含Function)
  */
console.log(Function.__proto__===Function.prototype)
/*
  3. Object的原型对象是原型链尽头
   */
console.log(Object.prototype.__proto__) // null

原型链的属性问题

1. 读取对象的属性值时: 会自动到原型链中查找
2. 【设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值】
3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上

【案例】

function Fn() {

}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1) //xxx

var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)  //xxx yyy

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.setName = function (name) {
    this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)

var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__) // true

instanceof与原型链

1. instanceof是如何判断的?
  * 表达式: A instanceof B
  * 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
2. Function是通过new自己产生的实例

【案例】

/*
  案例1
   */
function Foo() {  }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true

/*
  案例2
   */
console.log(Object instanceof Function) // true
console.log(Function instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Object) // true

function Foo() {}
console.log(Object instanceof  Foo) // false

【下面这个图务必搞懂】

image-20220620182306573

面试题

理解上面的图很快就能做出来

/*
    测试题1
    */
function A() {

}
A.prototype.n = 1

var b = new A()

A.prototype = {
    n: 2,
    m: 3
}

var c = new A()
console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3


/*
        测试题2
        */
function F() { }
Object.prototype.a = function () {
    console.log('a()')
}
Function.prototype.b = function () {
    console.log('b()')
}

var f = new F()
f.a() //ok
// f.b() //error
F.a() //ok
F.b() //ok
// console.log(f)
// console.log(Object.prototype)
// console.log(Function.prototype)

执行上下文

变量提升与函数提升

1. 变量声明提升
  * 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  * 值: undefined
2. 函数声明提升
  * 通过function声明的函数, 在之前就可以直接调用
  * 值: 函数定义(对象)
3. 问题: 变量提升和函数提升是如何产生的?

【案例】

/*
  面试题 : 输出 undefined
   */
var a = 3
function fn () {
    console.log(a)
    var a = 4
    }
fn()

console.log(b) //undefined  变量提升
fn2() //可调用  函数提升
// fn3() //不能  变量提升

var b = 3
function fn2() {
    console.log('fn2()')
}

var fn3 = function () {
    console.log('fn3()')
}

执行上下文

1. 代码分类(位置)
  * 全局代码
  * 函数(局部)代码
2. 全局执行上下文
  * 在执行全局代码前将window确定为全局执行上下文
  * 对全局数据进行预处理
    * var定义的全局变量==>undefined, 添加为window的属性
    * function声明的全局函数==>赋值(fun), 添加为window的方法
    * this==>赋值(window)
  * 开始执行全局代码
3. 函数执行上下文
  * 在【调用函数】, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  * 对局部数据进行预处理
    * 形参变量==>赋值(实参)==>添加为执行上下文的属性
    * arguments==>赋值(实参列表), 添加为执行上下文的属性
    * var定义的局部变量==>undefined, 添加为执行上下文的属性
    * function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    * this==>赋值(调用函数的对象)
  * 开始执行函数体代码

【案例】

console.log(a1, window.a1)
window.a2()
console.log(this)

var a1 = 3
function a2() {
console.log('a2()')
}
console.log(a1)

执行上下文栈

1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window

【案例】

var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)
}
var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}
bar(10)
// bar(10)

【流程分析图】

image-20220620221429648

面试题

/*
   测试题1:  先执行变量提升, 再执行函数提升
   */
function a() { }
var a
console.log(typeof a) // 'function'

/*
  测试题2:
*/
if (!(b in window)) {
    var b = 1
}
console.log(b) // undefined

/*
测试题3:
*/
var c = 1
function c(c) {
    console.log(c)
}
console.log(c)
//c(2) // 报错

//测试3的是因为先c变量提升,再c函数提升,最后给c赋值1   所以c已经变为一个数字了

作用域和作用域链

全局作用域和函数作用域

1. 理解
  * 就是一块"地盘", 一个代码段所在的区域
  * 它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
  * 全局作用域
  * 函数作用域
  * 没有块作用域(ES6有了)
3. 作用
  * 隔离变量,不同作用域下同名变量不会有冲突

【案例】

//没块作用域
if (false) {
    var c = 3 //这里会变量提升到全局,如果注释这行,打印c则报错
}
console.log(c) //undefined

var a = 10,b = 20
function fn(x) {
    var a = 100,c = 300;
    console.log('fn()', a, b, c, x)
    function bar(x) {
        var a = 1000,
            d = 400
        console.log('bar()', a, b, c, d, x) 
    }

    bar(100) //1000 20 300 400 100
    bar(200) //1000 20 300 400 200
}
fn(10)

【图解】

image-20220621091643046

作用域与执行上下文

1. 区别1
  * 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  * 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  * 函数执行上下文是在调用函数时, 函数体代码执行之前创建
2. 区别2
  * 【作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化】
  * 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
3. 联系
  * 执行上下文(对象)是从属于所在的作用域
  * 全局上下文环境==>全局作用域
  * 函数上下文环境==>对应的函数使用域

【案例】

var a = 10,b = 20
function fn(x) {
    var a = 100,c = 300;
    console.log('fn()', a, b, c, x)
    function bar(x) {
        var a = 1000,d = 400
        console.log('bar()', a, b, c, d, x)
    }

    bar(100)
    bar(200)
}
fn(10)

【图解】

image-20220621091942968

作用域链

1. 理解
  * 多个上下级关系的作用域形成的链, 它的方向是从下向上的(【从内到外】)
  * 查找变量时就是沿着作用域链来查找的
2. 查找一个变量的查找规则
  * <1>在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入<2>
  * <2>在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入<3>
  * <3>再次执行2>的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

【案例】

 var a = 1
  function fn1() {
    var b = 2
    function fn2() {
      var c = 3
      console.log(c) //3
      console.log(b) //2
      console.log(a) //1
      //console.log(d) //error
    }
    fn2()
  }
  fn1()

【图解】

image-20220621092803164

面试题1

var x = 10
function fn() {
    console.log(x) //函数定义的时候作用域已经确定,且不会变化
}
function show(f) {
    var x = 20
    f()
}
show(fn) //10

面试题2

var fn = function() {
    console.log(fn)
}
fn() //打印fn函数
var obj = {
    fn2: function() {
        console.log(fn2) //全局中找不到fn2这个函数
    }
}
//obj.fn2() //报错

闭包

循环加事件监听问题

<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>00_引入</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  /*
   * var i 是全局的,for先执行完后,再调用点击事件的时候已经是最后一个i了 
   * btns.length是伪数组,每次都要计算,为了提高性能,可以按下面这样写
   */
  // for (var i = 0,length=btns.length; i < length; i++) {
  //   var btn = btns[i]
  //   btn.onclick = function () {
  //     alert('第'+(i+1)+'个')
  //   }
  // }
  // console.log(i) //3
  
  //循环执行时,把索引加到btn上,这样就不会出现都是4了
  // for (var i = 0,length=btns.length; i < length; i++) {
  //   var btn = btns[i]
  //   //将btn所对应的下标保存在btn上
  //   btn.index = i
  //   btn.onclick = function () {
  //     alert('第'+(this.index+1)+'个')
  //   }
  // }

  //利用闭包
  for (var i = 0,length=btns.length; i < length; i++) {
    (function (j) {
      var btn = btns[j]
      btn.onclick = function () {
        alert('第'+(j+1)+'个')
      }
    })(i)
  }

</script>
</body>
</html>

闭包的理解

1. 如何产生闭包?
  * 【当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包】
2. 闭包到底是什么?
  * 使用chrome调试查看
  * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  * 理解二: 包含被引用变量(函数)的对象(极少数人)
  * 注意: 闭包存在于嵌套的内部函数中
3. 产生闭包的条件?
  * 函数嵌套
  * 内部函数引用了外部函数的数据(变量/函数)

【案例】

function fun1(){
    var a = 1
    //执行函数定义就会产生闭包(不用调用内部函数)
    function fun2(){ 
        console.log(a)
    }
    return fun2 //新版的google要return不然看不到闭包(fun2)
}
fun1()

image-20220621141020209

常见的闭包

1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用

【案例】

// 1. 将函数作为另一个函数的返回值
function fn1() {
    var a = 2
    function fn2() {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1()
f() // 3
f() // 4

// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
    setTimeout(function () {
        alert(msg)
    }, time)
}
showDelay('atguigu', 2000)

//总结起来还是那句话:一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)

闭包的作用

1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:
  1. 函数执行完后, 函数内部声明的局部变量是否还存在?  一般是不存在, 存在于闭包中的变量才可能存在
  2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它

【案例】

function fn1() {
    var a = 2
    function fn2() {
        a++
        console.log(a)
        // return a
    }
    function fn3() {
        a--
        console.log(a)
    }
    return fn3
}
var f = fn1()
f() // 1
f() // 0

闭包的生命周期

1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时

【案例】

function fn1() {
    //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
    var a = 2
    function fn2 () {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

闭包的应用

自定义js模块

第一种定义:【myModule.js】

function myModule() {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

第二种定义:【myModule2.js】 jquery就是这么写的!!!

//利用立即执行函数+window全局,通过myModule2.xxx 来直接调用,比第一种更方便
(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

闭包的优缺点

1. 缺点
  * 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  * 容易造成内存泄露
2. 解决
  * 能不用闭包就不用
  * 及时释放

【案例】

function fn1() {
    var arr = new Array[100000]
    function fn2() {
        console.log(arr.length)
    }
    return fn2
}
var f = fn1()
f()

f = null //让内部函数成为垃圾对象-->回收闭包

面试题1

//代码片段一
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());  //? The Window

//代码片段二
var name2 = "The Window";
var object2 = {
    name2 : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name2;
        };
    }
};
alert(object2.getNameFunc()()); //?  My Object

面试题2

有难度,先了解一些

function fun(n,o) {
    console.log(o)
    return {
        fun:function(m){
            return fun(m,n)
        }
    }
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)//undefined,0,0,0

var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2

var c = fun(0).fun(1)
c.fun(2)
c.fun(3)//undefined,0,1,1

对象的创建方式

这部分内容和js基础部分重复了

Object构造函数模式

方式一: Object构造函数模式
  * 套路: 先创建空Object对象, 再动态添加属性/方法
  * 适用场景: 起始时不确定对象内部数据
  * 问题: 语句太多

【案例】

/*
  一个人: name:"Tom", age: 12
   */
// 先创建空Object对象
var p = new Object()
p = {} //此时内部数据是不确定的
// 再动态添加属性/方法
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
    this.name = name
}

//测试
console.log(p.name, p.age)
p.setName('Bob')
console.log(p.name, p.age)

对象字面量

方式二: 对象字面量模式
  * 套路: 使用{}创建对象, 同时指定属性/方法
  * 适用场景: 起始时对象内部数据是确定的
  * 问题: 如果创建多个对象, 有重复代码

【案例】

var p = {
    name: 'Tom',
    age: 12,
    setName: function (name) {
        this.name = name
    }
}

//测试
console.log(p.name, p.age)
p.setName('JACK')
console.log(p.name, p.age)

var p2 = {  //如果创建多个对象代码很重复
    name: 'Bob',
    age: 13,
    setName: function (name) {
        this.name = name
    }
}

工厂模式

方式三: 工厂模式
  * 套路: 通过工厂函数动态创建对象并返回
  * 适用场景: 需要创建多个对象
  * 问题: 对象没有一个具体的类型, 都是Object类型

【案例】

function createPerson(name, age) { //返回一个对象的函数===>工厂函数
    var obj = {
        name: name,
        age: age,
        setName: function (name) {
            this.name = name
        }
    }

    return obj
}

// 创建2个人
var p1 = createPerson('Tom', 12)
var p2 = createPerson('Bob', 13)

// p1/p2是Object类型

function createStudent(name, price) {
    var obj = {
        name: name,
        price: price
    }
    return obj
}
var s = createStudent('张三', 12000)
// s也是Object

自定义构造函数模式

方式四: 自定义构造函数模式
  * 套路: 自定义构造函数, 通过new创建对象
  * 适用场景: 需要创建多个类型确定的对象
  * 问题: 每个对象都有相同的数据, 浪费内存

【案例】

//定义类型
function Person(name, age) {
    this.name = name
    this.age = age
    this.setName = function (name) {
        this.name = name
    }
}
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age)
console.log(p1 instanceof Person)

function Student (name, price) {
    this.name = name
    this.price = price
}
var s = new Student('Bob', 13000)
console.log(s instanceof Student)

var p2 = new Person('JACK', 23)
console.log(p1, p2)

构造函数+原型的组合模式

方式六: 构造函数+原型的组合模式
  * 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
  * 适用场景: 需要创建多个类型确定的对象

【案例】

function Person(name, age) { //在构造函数中只初始化一般函数
    this.name = name
    this.age = age
}
Person.prototype.setName = function (name) {
    this.name = name
}

var p1 = new Person('Tom', 23)
var p2 = new Person('Jack', 24)
console.log(p1, p2)

继承方式

原型链继承

方式1: 原型链继承
  1. 套路
    1. 定义父类型构造函数
    2. 给父类型的原型添加方法
    3. 定义子类型的构造函数
    4. 【创建父类型的对象赋值给子类型的原型】
    5. 【将子类型原型的构造属性设置为子类型】
    6. 给子类型原型添加方法
    7. 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    1. 子类型的原型为父类型的一个实例对象
    
  这种方式只实现了方法的继承

【案例】

//父类型
function Supper() {
    this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function () {
    console.log(this.supProp)
}

//子类型
function Sub() {
    this.subProp = 'Sub property'
}

// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper() //关键1
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub//关键2
Sub.prototype.showSubProp = function () {
    console.log(this.subProp)
}

var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()

console.log(sub)  // Sub

【图解】

image-20220621165514090

借用构造函数继承

方式2: 借用构造函数继承(假的)
1. 套路:
  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造
2. 关键:
  1. 在子类型构造函数中通用call()调用父类型构造函数
  
这种方式只实现了属性的继承

【案例】

function Person(name, age) {
    this.name = name
    this.age = age
}
function Student(name, age, price) {
    Person.call(this, name, age)  // 相当于: this.Person(name, age)
    /*this.name = name
    this.age = age*/
    this.price = price
}

var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)

组合继承

方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承【继承方法】
2. 利用super()借用父类型构建函数初始化相同属性【继承属性】

【案例】

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.setName = function (name) {
    this.name = name
}

function Student(name, age, price) {
    //核心代码1
    Person.call(this, name, age)  // 为了得到属性
    
    this.price = price
}
//核心代码2
Student.prototype = new Person() // 为了能看到父类型的方法
//核心代码3
Student.prototype.constructor = Student //修正constructor属性

Student.prototype.setPrice = function (price) {
    this.price = price
}

var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

事件的轮询机制

image-20220725165906639

image-20220725165924721

------ 本文结束,感谢您的阅读 ------
本文作者: 程序员青阳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。