JavaScript 核心知识点

2021/5/17 JavaScript珠峰架构

# 1. 栈

者,存储货物或供旅客住宿的地方,可引申为仓库

(类似好多的计算机系统中的概念)是计算机科学工作者抽象出来的概念(客观世界中只有内存这个东西),所谓科学,其实是从认识和实践客观事物中抽象出一定规律,并在这个规律中进行演绎,最后反过来指导认识和实践客观事物的过程。

所谓科学,即类似于取之于民,用之于民

# 1.1 数据结构中的栈

  • 栈是一组数据的存放方式,特点是先进后出,后进先出

instack

outstack

想想羽毛球桶 -> 拿羽毛球的规则是怎样的……

方法名 操作
push() 添加新元素到栈顶
pop() 移除栈顶的元素,同时返回被移除的元素
class Stack {
    private items: number[] = [];
    // 添加元素到栈顶,也就是栈的末尾
    push(element: number) {
        this.items.push(element);
    }
    // 栈的后进先出原则,从栈顶出栈
    pop(): number | undefined {
        return this.items.pop();
    }
}

let stack = new Stack();
// stack.push(1);
// stack.push(2);
// stack.push(3);
console.log(stack.pop());
// console.log(stack.items) // items 只能在 class 里边被访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 1.2 代码的运行方式

  • 表示函数的一层层调用

image-20210516205937505

代码执行完,意味着 anonymous 也就消失了 -> 这 TM 就是 Call Stack

# 1.3 内存区域

  • 栈也是存放数据的一种内存区域
  • 程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做 stack(栈),另一种叫做 heap(堆)
    • stack 是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小
    • heap 是没有结构的,数据可以任意存放。因此,stack 的寻址速度要快于 heap
  • 只要是局部的、占用空间确定的数据,一般都存放在 stack 里面,否则就放在 heap 里面,所有的对象都存放在 heap

内存对于 cpu 来说就是一段连续的数组(整个内存容量) -> 栈就在这个连续的数组中的某一段

function task() {
    var a = 1;
    var b = 2;
    var c = {
        name: 'zhufeng',
        age: 10
    }
}
task();
1
2
3
4
5
6
7
8
9

memoryposition

如果一行就是一个数组单元,那么整个仓库就是个内存条 -> 如果一行就是一个堆 -> 如果一行就是一个对象,像 A1 就是它的内存地址,那么整个仓库就是一个堆

# 2. 队列

  • 队列是一种操作受限制的线性表
  • 特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作
  • 进行插入操作的端称为队尾,进行删除操作的端称为队头
  • 因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出线性表

inqueue.png

outqueue.png

class Queue {
    private items: number[] = [];
    // 添加元素到栈顶,也就是栈的末尾
    enqueue(element: number) {
        this.items.push(element);
    }
    dequeue() {
        return this.items.shift();
    }
}

let queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.dequeue());//1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3. 数据类型

JS 中有七种基本数据类型

  • 六种基本数据类型 Boolean Number String Null Undefined Symbol
  • 一种引用类型 Object -> {} [] /^$/ new Date() Math
类型
Boolean true 或 false
Null null
Undefined undefined
Number 数字
String 字符串
Symbol 符号类型

# 4. 执行上下文

# 4.1 如何存储

  • 当函数运行时,会创建一个执行环境,这个执行环境就叫执行上下文 (Execution Context)
  • 执行上下文中会创建一个对象叫作变量对象 (Value Object), 基础数据类型都保存在变量对象中
  • 引用数据类型的值保存在堆里,我们通过操作对象的引用地址来操作对象

image-20210516234530226

valueobject

ECStack -> EC(Global) -> EC(Local)

代码在执行前,浏览器会创建一个可执行环境,即 ECStack(执行环境栈,分配一块栈内存给程序使用) -> script里边的代码执行,在执行前会初始化这件事情:GOVO[[scope]] -> VO ,执行开始会确定 scopeChain -> 上一级的空 VO

函数的创建,会初始化[[scope]](确定自己是哪个作用域内被创建的) -> 而函数的执行,会确定 scopeChain -> 上一级的 VOVOthis

闭包 -> 保护作用(外界不能访问函数内部的 VO)、保存作用(函数执行完,EC 会被释放,如果 VO里边有东西 xxx 被全局某个东西间接使用着,那么这个 xxx 是会被保存下来的)

# 4.2 如何复制

# 4.2.1 基本数据

  • 基本数据类型复制的是值本身
var a = 1;
var b = a;
b = 2;
console.log(a);
1
2
3
4
var ExecuteContext = {
    VO: { a: 1 }
};

ExecuteContext.VO.b = ExecuteContext.VO.a;
ExecuteContext.VO.b = 2;
console.log(ExecuteContext.VO.a);
1
2
3
4
5
6
7

changebasictype

叫你去用这个东西 -> 你得自己买

# 4.2.2 引用数据

  • 引用数据类型复制的是引用地址指针
var m = { a: 1, b: 2 };
var n = m;
n.a = 10;
console.log(m.a);
1
2
3
4
var ExecuteContext = {
    VO: { m: { a: 1, b: 2 } }
};

ExecuteContext.VO.n = ExecuteContext.VO.m;
ExecuteContext.VO.n.a = 10;
console.log(ExecuteContext.VO.m.a);
1
2
3
4
5
6
7

copyrefer

一起用这个东西

# 5. 多个执行上下文栈

# 5.1 执行上下文分类

  • JS 代码在执行的时候会进入一个执行上下文,可以理解为当前代码的运行环境
  • 在 JS 中运行环境主要分为全局执行上下文环境和函数环执行上下文环境
    • 全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的 window 对象,我们能通过 this 直接访问到它
    • window 对象还是 var 声明的全局变量的载体。我们通过 var 创建的全局对象,都可以通过 window 直接访问

全局执行上下文真得是 window 对象吗?那 window.this === undefined怎么说? -> thiswindow是同级的啊!

# 5.2 多个执行上下文

  • 在 JS 执行过程会产出多个执行上下文,JS 引擎会有栈来管理这些执行上下文
  • 执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有 LIFO(Last In First Out 后进先出,也就是先进后出)的特性
  • 栈底永远是全局上下文,栈顶为当前正在执行的上下文
  • 当开启一个函数执行时会生成一个新的执行上下文并放入调用栈,执行完毕后会自动出栈

image-20210517104521839

var globalExecuteContext = {
    VO: { setTimeout: 'setTimeout' }
}
var executeContextStack = [globalExecuteContext];
var oneExecuteContext = {
    VO: { a: 1 }
}
executeContextStack.push(oneExecuteContext);
var twoExecuteContext = {
    VO: { b: 2 }
}
executeContextStack.push(twoExecuteContext);
var threeExecuteContext = {
    VO: { c: 3 }
}
executeContextStack.push(threeExecuteContext);
console.log(executeContextStack);

executeContextStack.pop();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Web APIs 难道不是 window 旗下的吗? -> 所谓的 GO 就是 VO 吗? -> GO、VO、window、this、[[scope]] -> window指向GOVO里边存储的是被声明的变量,let/const声明的变量不会放到 GO 旗下,var 声明的变量则会放到 GO 旗下,所以 window旗下会有 var 声明的变量,但不管怎样,全局声明的变量,都是全局变量!

# 6. 执行上下文生命周期

# 6.1 生命周期有两个阶段

  • 一个新的执行上下文的生命周期有两个阶段
    • 创建阶段
      • 创建变量对象
      • 确定作用域链
      • 确定this指向
    • 执行阶段
      • 变量赋值
      • 函数赋值
      • 代码执行

# 6.2 变量对象

  • 变量对象会保存变量声明 (var)、函数参数 (arguments)、函数定义 (function)
    • 变量对象会首先获得函数的参数变量和值
    • 获取所有用function进行的函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖
    • 再依次所有的var关键字进行的变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为undefined, 如果变量名已经存在,则会跳过,并不会修改原属性值,let声明的变量并不会在此阶段进行处理
  • 函数声明优先级更高,同名的函数会覆盖函数和变量,但同名var变量并不会覆盖函数。执行阶段重新赋值可以改变原有的值

# 6.2.1 基本类型

console.log(a);
var a = 1;
1
2
var a = undefined; // 变量提升  -> 创建并初始化
console.log(a); 
a = 1;
1
2
3

# 6.2.2 变量提升

  • 正常编写
var a = 1;
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
console.log(a);
fn(1);
var fn = 'var_fn';
console.log(fn);
// 1
// new_fn
// var_fn
1
2
3
4
5
6
7
8
9
10
11
  • 真正执行

image-20210517130038956

  • 上下文
// 创建阶段
var globalEC = {
    VO: {
        ...arguments,
        a: () => { console.log('fn_a'); },
        fn: () => { console.log('new_fn'); }
    }
}
var ECStack = [globalEC];

//执行阶段
globalEC.VO.a = 1;
console.log(globalEC.VO.a);
globalEC.VO.fn();
globalEC.VO.fn = 'var_fn';
console.log(globalEC.VO.fn);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.2.3 激活对象

  • 在函数的调用栈中,如果当前执行上下文处于函数调用栈的顶端,则意味着当前上下文处于激活状态,此时变量对象称为活动对象 (AO,Activation Object) VO -> AO
  • 活动变量包含变量对象所有的属性,并有包含this指针
function one(m) {
    function two() {
        console.log('two');
    }
}
one(1);

// 执行阶段 VO => AO
let VO = AO = {
    m:1,
    two: () => { console.log('two'); },

}
let oneEC={
    VO,
    this: window,
    scopeChain:[VO,globalVO]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.2.4 全局上下文的变量对象

  • 在浏览器里,全局对象为window
  • 全局上下文的变量对象为window, 而且这个变量对象不能激活变成活动对象
  • 只在窗口打开,全局上下文会一直存在,所有的上下文都可以直接访问全局上下文变量对象上的属性
  • 只有全局上下文的变量对象允许通过 VO 的属性名称来间接访问,在函数上下文中是不能直接访问 VO 对象的
  • 未进入执行阶段前,变量对象中的属性都不能访问!但是进入到执行阶段之后,变量对象转变成了活动对象,里面的属性都能被访问了,对于函数上下文来讲,活动对象与变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期

# 7. 作用域

# 7.1 作用域

  • 在 JS 中,作用域是用来规定变量访问范围的规则
function one() {
    var a = 1;
}
console.log(a);
1
2
3
4

# 7.2 作用域链

  • 作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问

# 7.2.1 例 1

function one() {
    var a = 1;
    function two() {
        var b = 2;
        function three() {
            var c = 3;
            console.log(a, b, c);
        }
        three();
    }
    two();
}
one(); 
1
2
3
4
5
6
7
8
9
10
11
12
13

image-20210517191640607


// 1. 创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}` }
var globalExecuteContext = {
    VO: globalExecuteContextVO,
    scopeChain: [globalExecuteContextVO]
}
var executeContextStack = [globalExecuteContext];
//2. 执行 one,创建 one 执行上下文
var oneExecuteContextVO = {
    a: 1,
    two: `()=>{var b = 2 ;}`
}
var oneExecuteContext = {
    VO: oneExecuteContextVO,
    scopeChain: [oneExecuteContextVO, globalExecuteContext.VO]
}
//2. 执行 two,创建 two 执行上下文
var twoExecuteContextVO = {
    b: 2,
    three: `()=>{var c = 3 ;}`
}
var twoExecuteContext = {
    VO: twoExecuteContextVO,
    scopeChain: [twoExecuteContextVO, oneExecuteContext.VO, globalExecuteContext.VO]
}
//3. 执行 three,创建 three 执行上下文
var threeExecuteContextVO = {
    c: 3
}
var threeExecuteContext = {
    VO: threeExecuteContextVO,
    scopeChain: [threeExecuteContextVO, twoExecuteContext.VO, oneExecuteContext.VO, globalExecuteContext.VO]
}

// 遍历作用域链,从自身开始,一直往上一级走……
function getValue(varName) {
    for (let i = 0; i < threeExecuteContext.scopeChain.length; i++) {
        if (varName in threeExecuteContext.scopeChain[i]) {
            return threeExecuteContext.scopeChain[i][varName];
        }
    }
}
//console.log(a, b, c);
console.log(
    getValue('a'),
    getValue('b'),
    getValue('c'),
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

在执行某个函数的时候,会创建执行上下文,此时会有个创建阶段,用于初始化 VOscopeChain,等到真正执行这个函数的时候,VO也就成了AO,表示此时的 Call Stack 的栈顶是这个函数的执行上下文!

# 7.2.2 例 2

  • scopeChain其实是在创建函数的时候确定的
function one() {
    var a = 1;
    function two() {
        console.log(a);
    }
    return two;
}
var a = 2;
var two = one();
two(); // 1
1
2
3
4
5
6
7
8
9
10

// 1. 创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}`, a: undefined, two: undefined }
var globalExecuteContext = {
    VO: globalExecuteContextVO,
    scopeChain: [globalExecuteContextVO]
}
//2. 开始执行
globalExecuteContextVO.a = 2;
//3. 开始执行 one
var oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
    VO: oneExecuteContextVO,
    scopeChain: [oneExecuteContextVO, globalExecuteContextVO]
}
oneExecuteContextVO.a = 1;
//4. 给 two 赋值
globalExecuteContextVO.two = oneExecuteContextVO.two;
//5. 执行 two
var twoExecuteContextVO = {}
var twoExecuteContext = {
    VO: twoExecuteContextVO,
    //scopeChain 是在创建此函数据的时候就决定了,跟在哪里执行无关
    scopeChain: [twoExecuteContextVO, oneExecuteContextVO, globalExecuteContextVO]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

当你执行 two 函数的时候,它在找 a 的值,是从作用域链开始往上找的,而作用域链的初始化,是在函数创建时确定的!

# 8. 闭包

  • 闭包有两部分组成,一个是当前的执行上下文 A,一个是在该执行上下文中创建的函数 B
  • 当 B 执行的时候引用了当前执行上下文 A 中的变量就会产出闭包
  • 当一个值失去引用的时候就会会标记,被垃圾收集回收机回收并释放空间
  • 闭包的本质就是在函数外部保持内部变量的引用,从而阻止垃圾回收
  • 调用栈的并不会影响作用域链,函数调用栈是在执行时才确定,而作用域规则是在代码编译阶段就已经确定了
  • MDN 定义:闭包是指这样的作用域foo, 它包含了一个函数fn,这个函数fn1可以调用被这个作用域所封闭的变量a、函数等内容

# 8.1 闭包

  • Call Stack为当前的函数调用栈
  • Scope为当前正在被执行函数的作用域链
  • Local为当前的活动对象
function one() {
    var a = 1;
    var b = 2;
    function two() {
        var c = 3;
        debugger;
        console.log(a,c);
    }
    return two;
}
let two = one();
two();
1
2
3
4
5
6
7
8
9
10
11
12

image-20210517203041358

function one() {
    var a = 1;
    var b = 2;
    function two() {
        debugger;
        console.log(a);
    }
    two();
}
one();
1
2
3
4
5
6
7
8
9
10

image-20210517204327489

关于 Scope 旗下的 Script 测试:

image-20210517204940503

函数 one 也在 Global 里边 -> Script存储的是用 letconst 声明的变量

Closure是特殊的,它旗下的值是可以被保存下来的,相当于在 two 的作用链途中留下了版本记录或者说痕迹

function one() {
    var a = 1;
    var b = 2;
    function two() {
        var c = 3;
        debugger;
        console.log(++a);
    }
    return two;
}
let x = 666
var y = 777
const z = 888
let two = one();
two(); // 2
// 第二次执行 two,two 中的 a 会找到上一次执行 two 所记录的 a 值
two(); // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.2 闭包优化

  • 中间没用到的变量闭包会被忽略
function one() {
    var a = 1;
    function two() {
        var b = 2;
        function three() {
            var c = 3;
            debugger;
            console.log(a, b, c);
        }
        three();
    }
    two();
}
one();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

image-20210517205700523

function one() {
    var a = 1;
    function two() {
        var b = 2;
        function three() {
            var c = 3;
            debugger;
            console.log(a, c);
        }
        three();
    }
    two();
}
one();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

image-20210517205924886

# 8.3 arguments

function log(a, b) {
    debugger;
    console.log(a, b);
}
log(1, 2);
1
2
3
4
5

image-20210517210318858

# 9. var 和 let

  • JS 中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称 ES6) 中新增了块级作用域
  • 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域

# 9.1 ES5 问题

# 9.1.1 全局变量

  • 在 if 或者 for 循环中声明的变量会变成全局变量
for(var i=0;i<=5;i++){
    console.log("hello");
}
console.log(i); //5
1
2
3
4

# 9.1.2 内层变量可能会覆盖外层变量

var a = 1;
function fn() {
  console.log(a);
  if (false) {
      var a = 2;
  }
}
fn(); //undefined
1
2
3
4
5
6
7
8

var a 会变量提升

# 9.2 let

  • 允许块级作用域任意嵌套
  • 外层作用域无法读取内层作用域的变量
  • 内层作用域可以定义外层作用域的同名变量
  • 函数本身的作用域在其所在的块级作用域之内
'use strict'
function fn() {
    console.log("out");
}
(function () {
    if (false) {
        function fn() {
            console.log("in");
        }
    }
    fn(); // out
}());
1
2
3
4
5
6
7
8
9
10
11
12

去掉 'use strict' 的话,Uncaught TypeError: fn is not a function,因为 fnundefined 值 -> 疑惑:为啥 fn 是默认就一个 undefined 值呢?难道 fn 已经提升了?可如果提升了,难道 fn 不是一个函数值吗?

# 9.3 var&let&const

  • var 定义的变量没有块的概念,可以跨块访问,不能跨函数访问,有变量提升,可重复声明
  • let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明
  • let 声明的变量只在块级作用域内有效,不存在变量提升,而是绑定在暂时性死区
  • 或者说 let 变量提升了,但是在 let 声明变量前不能使用该变量,这特性叫暂时性死区 (temporal dead zone)
  • 如果有重复变量let会在编译阶段报错

let 声明的变量是提升了,只是完成了变量的创建阶段罢了!你不能在还未初始化它之前使用它!

# 9.3.1 暂时性死区

// 不存在变量提升
'use strict';
function func(){
    console.log(i);
    let i;
};
func(); // 报错:Uncaught ReferenceError: Cannot access 'i' before initialization
1
2
3
4
5
6
7

# 9.3.2 全局变量

  • ES5 声明变量只有两种方式:var 和 function
  • ES6 有let、const、import、class再加上 ES5 的var、function共有六种声明变量的方式
  • 浏览器环境中顶层对象是 window,Node 中是 global 对象
  • ES5 中 顶层对象的属性等价于全局变量
  • ES6 中var、function声明的全局变量,依然是顶层对象的属性;let、const、class声明的全局变量不属于顶层对象的属性

import 声明的变量也不属于顶层对象的属性

# 9.3.3 题目

'use strict' 
var a = 1;
console.log(a);//1
{
    console.log(a);// function a() {}
    function a() {
        console.log(1);
    }
}
console.log(a);// 1
1
2
3
4
5
6
7
8
9
10

非严格模式:

var a = 1;
console.log(a);//1
{
    console.log(a);// function a() {}
    function a() {
        console.log(1);
    }
}
console.log(a);// function a() {}
1
2
3
4
5
6
7
8
9

# 10. this

  • 当前函数的 this 是在被调用的时候才能确定的
  • 如果当前的执行上下文处于调用栈的栈顶,这个时候变量对象变成了活动对象,THIS 指针才能确定

# 10.0 全局对象

  • 全局对象 this 指向本身
var a=1; // 声明绑定变量对象,但在全局环境中,变量对象就是全局对象
this.b=2; // this 绑定全局对象
c=3; // 赋值操作 隐式绑定
1
2
3

# 10.1 用点调用

  • 在一个函数上下文中,this 由函数的调用者提供,由调用函数的方式来决定指向
  • 如果是函数执行,如果前面有点,那么点前面是谁this就是谁
let obj = {
    getName(){
        console.log(this);
    }
};
obj.getName();
1
2
3
4
5
6

# 10.2 直接调用

  • 如果没有. ,this 就是 window(严格模式下是 undefined), 自执行函数中的 this 一般都是 window
let obj = {
    getName(){
        console.log(this);
    }
};
let getName = obj.getName;
getName();
1
2
3
4
5
6
7

# 10.3 绑定事件

  • 给元素绑定事件的时候,绑定的方法中的 this 一般是元素本身
container.addEventListener('click',function(){
    console.log(this);
});
1
2
3

# 10.4 箭头函数

  • 箭头函数没有自己的 this
  • 也没有 prototype
  • 也没有 arguments
  • 无法创建箭头函数的实例
let fn = () => {
    console.log(this);
    console.log(arguments);//Uncaught ReferenceError: arguments is not defined
}
console.log(fn.prototype);//undefined
fn();
new fn();//Uncaught TypeError: fn is not a constructor
1
2
3
4
5
6
7

# 10.5 构造函数

  • 构造函数中的 THIS 是当前类的实例
function fn(){

}
let obj = new fn();
1
2
3
4

# 10.6 call/apply/bind

  • call/apply/bind 可以改变函数中 this 的指向
  • 第一个参数是改变 this 指向(非严格模式下,传递 null/undefined 指向也是 window)
  • call 参数是依次传递,apply 是以数组的方式传递
!function (proto) {
    function getContext(context) {
        context = context || window;
        var type = typeof context;
        if (['number', 'string', 'boolean', 'null'].includes(type)) {
            context = new context.constructor(context);
        }
        return context;
    }
    function call(context, ...args) {
        context = getContext(context);
        context._fn = this;
        let result = context._fn(...args);
        delete context._fn;
        return result;
    }
    function apply(context, args) {
        context = getContext(context);
        context._fn = this;
        let result = context._fn(...args);
        delete context._fn;
        return result;
    }

    function bind(context, ...bindArgs) {
        return (...args) => this.call(context, ...bindArgs, ...args);
    }
    proto.call = call;
    proto.apply = apply;
    proto.bind = bind;
}(Function.prototype)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 11. 面向对象

# 11.1 原型链

# 11.1.1 一切皆对象

  • 对象就是一些属性的集合
  • 方法也是一种属性
  • 一切(引用类型)都是对象,对象是属性的集合
  • 函数和数组也是对象
  • 为什么 typeof function='function'
# 11.1.1.1 typeof
  • 检测数据类型 typeof返回的都是字符串
  • 基本数据类型 number string boolean undefined symbol
  • 引用类型 null {} [] /&$/ Date => object
console.log(typeof a);    // undefined
console.log(typeof 1);   // number
console.log(typeof 'zhufeng'); // string
console.log(typeof true);  // boolean
console.log(typeof Symbol('a'));  // symbol

console.log(typeof function () { });  //function

console.log(typeof [1, 2, 3]);  //object
console.log(typeof { name: 'zhufeng' });  //object
console.log(typeof null);  //object
console.log(typeof new Number(1));  //object
1
2
3
4
5
6
7
8
9
10
11
12

# 11.1.2 函数

  • 对象是通过函数创建的
  • 批量生产对象的函数Object
  • 实现私有和公有属性的封装
let obj = new Object();
obj.name='zhufeng';
obj.age = 10;
1
2
3

# 11.1.3 隐式原型

# 11.1.3.1 proto
  • 每个对象都有一个__proto__属性,指向创建该对象的函数的prototype
  • Object.prototype.__proto__指向的是null

image-20210518155721567

object_prototypes

o1Object是透过什么关联起来的?

# 11.1.3.2 自定义函数的 prototype
  • 自定义函数的prototype__proto__指向的就是Object.prototype

customefuntionprototype

# 11.1.3.3 自定义函数
  • 自定义函数Foo.__proto__指向Function.prototype
  • Functionprototype__proto__都指向Function.prototype
Function.__proto__ === Function.prototype // true -> 都是函数对象
let add = new Function('a','b','return a+b');
console.log(add(1,2));
1
2
3

所有构造函数的 __proto__ 都指向 Function.prototype

2functionprotype

3functionprotype

# 11.1.4 instanceof

  • instanceof 运算符的第一个参数是一个对象,第二个参数一般是一个函数
  • instanceof 的判断规则是:沿着对象的__proto__这条链来向上查找,如果能找到函数的prototype则返回 true, 否则返回 false

prototypechain

# 11.2 批量创建对象

  • 通过new来调用一个函数,这个函数就成为了构造函数,构造函数里可以对实例对象的私有属性赋值
  • 每个函数会有一个prototype属性,此原型对象上存放所有实例的公有方法
  • 若 new 的构造函数自己返回引用值,则以自己返回的为主,否则 返回创建的实例
  • create (opens new window)
function Person(name) {
    // 自有属性,也叫私有属性,而 private 类型的属性,叫为不可被外界访问的属性比较恰当
    // 私有 即 只有自己这个实例能访问的属性
    this.name = name;
}
// 公有属性
Person.prototype.getName = function () {
    console.log(this.name);
}
let person = new Person('zhufeng');
person.getName();
1
2
3
4
5
6
7
8
9
10
11

模拟 new 的操作:

Object.create = function (proto) {
    function F() {}
    F.prototype = proto;
    return new F();
};
function _new(clazz, ...args) {
    let _this = Object.create(clazz.prototype);
    let result = clazz.call(_this, ...args);
    if ((result != null && typeof result === 'object') || typeof result === 'function') {
        return result;
    }
    return _this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 11.3 继承

class Father {
  static staticFatherName = 'FatherName';
  static staticGetFatherName = function () {
    console.log(1, Father.staticFatherName);
  };
  constructor(public name:any) {
    this.name = name;
  }
  getName() {
    console.log(2, this.name);
  }
}
class Child extends Father {
  static staticChildName = 'ChildName';
  static staticGetChildName = function () {
    console.log(3, Child.staticChildName);
  };
  constructor(public name:any, public age:any) {
    super(name);
    this.age = age;
  }
  getAge() {
    console.log(4, this.age);
  }
}
let child = new Child('zhufeng', 10);
child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[LOG]: 2,  "zhufeng" 
[LOG]: 4,  10 
[LOG]: 1,  "FatherName" 
[LOG]: 3,  "ChildName" 
1
2
3
4
var _extends = (function () {
    var extendStatics = function (Child, Father) {
        return Object.setPrototypeOf(Child, Father);
    }
    return function (Child, Father) {
        extendStatics(Child, Father);
        function Temp() {
            this.constructor = Child;
        }
        Temp.prototype = Father.prototype;
        Child.prototype = new Temp();
    };
})();

var Father = (function () {
    function Father(name) {
        this.name = name;
    }
    Father.prototype.getName = function () {
        console.log(this.name);
    };
    Father.staticFatherName = "FatherName";
    Father.staticGetFatherName = function () {
        console.log(Father.staticFatherName);
    };
    return Father;
}());
//_super 父类构造函数
var Child = (function (_super) {
    _extends(Child, _super);

    function Child(name, age) {
        _super.call(this, name);//继承父类的实例私有属性
        this.age = age;
        return this;
    }
    Child.prototype.getAge = function () {
        console.log(this.age);
    };
    Child.staticChildName = "ChildName";
    Child.staticGetChildName = function () {
        console.log(Child.staticChildName);
    };
    return Child;
}(Father));

let child = new Child('zhufeng', 10);
console.log(child);

child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Father = /** @class */ (function () {
    function Father(name) {
        this.name = name;
    }
    Father.prototype.getName = function () {
        console.log(this.name);
    };
    Father.staticFatherName = "FatherName";
    Father.staticGetFatherName = function () {
        console.log(Father.staticFatherName);
    };
    return Father;
}());
var Child = /** @class */ (function (_super) {
    __extends(Child, _super);
    function Child(name, age) {
        var _this = _super.call(this, name) || this;
        _this.name = name;
        _this.age = age;
        return _this;
    }
    Child.prototype.getAge = function () {
        console.log(this.age);
    };
    Child.staticChildName = "ChildName";
    Child.staticGetChildName = function () {
        console.log(Child.staticChildName);
    };
    return Child;
}(Father));
var child = new Child('zhufeng', 10);
child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 11.3.6 原型链面试题

function Foo() {
  getName = function () {
      console.log(1);
  }
  return this;
}
Foo.getName = function () {
  console.log(2);
}
Foo.prototype.getName = function () {
  console.log(3);
}
var getName = function () {
  console.log(4);
}
function getName() {
  console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();//1
new Foo.getName();
new Foo().getName();
new new Foo().getName();
// 2 4 1 1 2 3 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 11.3.7 异步面试题

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
1
2
3
4
5
6
7
8

# 12. ES6

  • 词法环境 VS 静态作用域
  • 变量环境 (variableEnvironment) 和词法环境 (Lexical Environment)
  • 闭包

# 12.1 词法环境

  • let 原理
function fn() {
    var a = 1;
    let b = 2;
    {
        let b = 3;
        var c = 4;
        let d = 5;
        //console.log(a, b, c, d);//TODO
    }
    {
        let b = 6;
        let d = 7;
        //console.log(a, b, c, d);
    }
}
fn();

/**
 * 1.global 编译阶段
 */

let globalEC = {
    this: globalThis,
    outer: null,//外部的执行上下文,词法作用域就是静态作用域,就是指作用域是由代码中函数声明的位置来决定的
    variableEnvironment: {
        fn() { console.log(a, b, c, d) }
    },
    lexicalEnvironment: {}
}

/**
 * 2.fn 编译阶段
 */
let fnEC = {
    this: globalThis,
    outer: globalEC,
    variableEnvironment: { a: undefined, c: undefined },
    lexicalEnvironment: [{ b: undefined }]
}
/**
 * 3. 编译代码块 1
 */
fnEC.variableEnvironment.a = 1;
fnEC.lexicalEnvironment.b = 2;
fnEC.lexicalEnvironment.push({
    b: undefined,
    d: undefined
});
/**
 * 4. 执行代码块 1
 */

fnEC.lexicalEnvironment[1].b = 3;
fnEC.variableEnvironment.c = 4;
fnEC.lexicalEnvironment[1].d = 5;

console.log(getValue('a', fnEC), getValue('b', fnEC), getValue('c', fnEC), getValue('d', fnEC));
function getValue(name, ec) {
    for (let i = ec.lexicalEnvironment.length - 1; i >= 0; i--) {
        if (name in ec.lexicalEnvironment[i]) {
            return ec.lexicalEnvironment[i][name];
        }
    }
    let currentVariableEnvironment = ec.variableEnvironment;
    while (currentVariableEnvironment) {
        if (name in currentVariableEnvironment) {
            return currentVariableEnvironment[name];
        }
        currentVariableEnvironment = currentVariableEnvironment.outer;
    }
    return null;
}

/**
 * 5. 编译代码块 2
 */
fnEC.lexicalEnvironment.pop();
fnEC.lexicalEnvironment.push({
    b: undefined,
    d: undefined
});
/**
 * 5. 执行代码块 2
 */
fnEC.lexicalEnvironment[1].b = 6;
fnEC.lexicalEnvironment[1].d = 7;
console.log(getValue('a', fnEC), getValue('b', fnEC), getValue('c', fnEC), getValue('d', fnEC));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

是边执行,边编译吗?

# 12.2 静态作用域

/* let name = 'zhufeng'
{
    //ReferenceError: Cannot access 'name' before initialization
    console.log(name)
    let name = 'jiagou'
} */

function two() {
    console.log(a);
}
function one() {
    var a = 2;
    two();
}
var a = 1;
one();

let globalEC = {
    a: 1,
    one() { },
    two() { }
}
let twoEC = {
    this: globalThis,
    outer: globalEC,
    variableEnvironment: { a: 1, two() { console.log(a) } } //出生的地方
}
var twoEc = { outer: globalEC };
console.log(twoEC.outer.a);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 12.3 闭包

function one() {
    var a = 1;
    var name = 'one';
    function two() {
        var b = 2;
        var name = 'two';
        function three() {
            var c = 3;
            var name = 'three';
            return () => console.log(a, b, c);

        }
        return three();
    }
    return two();
}
var fn = one();
fn();
let globalEC = {
    this: globalThis,
    outer: null,
    variableEnvironment: { one() { } }
}
let oneEC = {
    this: globalThis,
    outer: globalEC.variableEnvironment,
    variableEnvironment: { a: 1, two() { }, name: 'one' }
}
let twoEC = {
    this: globalThis,
    outer: oneEC.variableEnvironment,
    variableEnvironment: { b: 2, three() { }, name: 'two' }
}
let threeEC = {
    this: globalThis,
    outer: twoEC.variableEnvironment,
    variableEnvironment: { c: 3, name: 'three' }
}
let fnEC = {
    this: globalThis,
    outer: globalEC,
    //Closure(three) Closure(two) Closure(one)
    closures: [{ a: 1 }, { b: 2 }, { c: 3 }],
    variableEnvironment: { c: 3 }
}
console.log(getValue('a', fnEC), getValue('b', fnEC), getValue('c', fnEC));

function getValue(name, ec) {
    for (let i = ec.closures.length - 1; i >= 0; i--) {
        if (name in ec.closures[i]) {
            return ec.closures[i][name];
        }
    }
    let currentVariableEnvironment = ec.variableEnvironment;
    while (currentVariableEnvironment) {
        if (name in currentVariableEnvironment) {
            return currentVariableEnvironment[name];
        }
        currentVariableEnvironment = currentVariableEnvironment.outer;
    }
    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

这就是闭包的本质吗?

# ★了解更多

➹:栈究竟在哪里? - FanMo 的回答 - 知乎 (opens new window)

➹:TypeScript: 游乐场 - 一个用于 TypeScript 和 JavaScript 的在线编辑器 (opens new window)

➹:04-复习上节课的作业题「详细讲解:VO、AO、GO 以及一些其它细节知识点」 - zf-fe (opens new window)

➹:闭包 - JavaScript - MDN (opens new window)

➹:JavaScript Scope and Closures | CSS-Tricks (opens new window)

➹:backbone.js - Chrome javascript tools: Global, Closure and Local Scope - Stack Overflow (opens new window)

➹:function - How do JavaScript closures work? - Stack Overflow (opens new window)

上次更新: 2022/3/30 17:08:51