JS高级



js进阶->上流JS

JS基础总结深入

数据类型的分类和判断

数据类型 定义 类型判断
基本(值)类型
Number 任意数值 typeof
String 任意字符串 typeof
Boolean true/false typeof
undefined undefined typeof/===
null null ===
对象(引用)类型
Object 任意对象 typeof/instanceof
Array 一种特别的对象(索引,内部是有序的) instanceof
Function 一种特别的对象(可以执行) typeof
  • 判断

    • typeof:
      • 可以区别: 数值, 字符串, 布尔值, undefined, function
      • 不能区别: null与对象, 一般对象与数组
    • instanceof
      • 专门用来判断对象数据的类型: Object, Array与Function
    • ===
      • 可以判断: undefined和null
  • 相关问题

  1. undefined与null的区别?
  • undefined代表没有赋值
  • null代表赋值了, 只是值为null
  1. 什么时候给变量赋值为null呢?
  • var a = null //初始赋值为null,表明将要赋值为对象,a将指向一个对象,
    但对象此时还没有确定
  • a = null //让a指向的对象成为垃圾对象(被垃圾回收器回收)
  1. 严格区别变量类型与数据类型?
  • js的变量本身是没有类型的, 变量的类型实际上是变量内存中数据的类型
  • 变量类型:
    • 基本类型: 保存基本类型数据
    • 引用类型: 保存对象地址值
  • 数据对象
    • 基本类型
    • 对象类型

数据-变量-内存的理解

  1. 什么是数据?

    • 存储于内存中代表特定信息的’东东’, 本质就是0101二进制
    • 具有可读和可传递的基本特性
    • 万物(一切)皆数据, 函数也是数据
    • 程序中所有操作的目标: 数据
      • 算术运算
      • 逻辑运算
      • 赋值
      • 调用函数传参
  2. 什么是内存?

    • 内存条通电后产生的存储空间(临时的)
    • 产生和死亡:
      内存条(集成电路板)==>通电==>产生一定容量的存储空间==>存储各种数据==>断电==>内存全部消失
    • 内存的空间是临时的, 而硬盘的空间是持久的
    • 一块内存包含2个数据
      • 内部存储的数据(一般数据/地址数据)
      • 内存地址值数据
    • 内存分类
      • 栈: 全局变量, 局部变量 (空间较小)
      • 堆: 对象 (空间较大)
  3. 什么是变量?

    • 值可以变化的量, 由变量名与变量值组成
    • 一个变量对应一块小内存, 变量名用来查找到内存,
      变量值就是内存中保存的内容
  4. 内存,数据, 变量三者之间的关系

    • 内存是一个容器, 用来存储程序运行需要操作的数据
    • 变量是内存的标识, 我们通过变量找到对应的内存,
      进而操作(读/写)内存中的数据
  • 相关问题
  1. var a = xxx, a内存中到底保存的是什么?

    • xxx是一个基本数据,保存的就是数据
    • xxx是一个对象,保存的是对象的地址值
    • xxx是一个变量,保存的内存内容可能是地址值,也可能是数据值
  2. 关于引用变量赋值问题

    • 2个引用变量指向同一个对象, 通过一个引用变量修改对象内部数据,
      另一个引用变量看到的是修改之后的数据
    • 2个引用变量指向同一个对象,让一个引用变量指向另一个对象,
      另一个引用变量还是指向前面的对象
  3. 在js调用函数时传递变量参数时, 是值传递还是引用传递?

    • 只有值传递, 没有引用传递, 传递的都是变量的值,
      只是这个值可能是基本数据, 也可能是地址(引用)数据
    • 如果后一种看成是引用传递, 那就值传递和引用传递都可以有
  4. JS引擎如何管理内存?

    1. 内存生命周期

      1. 分配小内存空间,得到他的使用权
      2. 存储数据。进行反复操作
      3. 不需要时将其释放/归还
    2. 释放内存

      • 为执行函数分配的栈空间内存: 函数执行完自动释放
      • 存储对象的堆空间内存: 当内存没有引用指向时,
        对象成为垃圾对象, 垃圾回收器后面就会回收释放此内存

对象的理解和使用

  1. 什么是对象?

    • 代表现实中的某个事物, 是该事物在编程中的抽象
    • 多个数据的集合体(封装体)
    • 用于保存多个数据的容器
  2. 为什么要用对象?

    • 便于对多个数据进行统一管理
  3. 对象的组成

    • 属性
      • 代表现实事物的状态数据
      • 由属性名和属性值组成
      • 属性名都是字符串类型, 属性值是任意类型
    • 方法
      • 代表现实事物的行为数据
      • 是特别的属性==>属性值是函数
  4. 如何访问对象内部数据?

    • .属性名: 编码简单, 但有时不能用
    • [‘属性名’]: 编码麻烦, 但通用
  • 相关问题
  1. 什么时候必须使用[‘属性名’]的方式?
    • 属性名不是合法的标识名
    • 属性名不确定

函数的理解和使用

  • 什么是函数?

    • 用来实现特定功能的, n条语句的封装体
    • 只有函数类型的数据是可以执行的, 其它的都不可以
  • 为什么要用函数?

    • 提高复用性
    • 便于阅读交流
  • 函数也是对象

    • instanceof Object===true
    • 函数有属性: prototype
    • 函数有方法: call()/apply()
    • 可以添加新的属性/方法
  • 函数的3种不同角色

    • 一般函数 : 直接调用
    • 构造函数 : 通过new调用
    • 对象 : 通过.调用内部的属性/方法
  • 匿名函数自调用:

    1
    2
    3
    (function(w, obj){
    //实现代码
    })(window, obj)
    • 专业术语为: IIFE (Immediately Invoked Function Expression)
      立即调用函数表达式
    • 作用
      • 隐藏内部实现
      • 不污染外部命名空间
      • 用它来编写js模块
  • 回调函数的理解

    • 什么函数才是回调函数?
      • 你定义的
      • 你没有调用
      • 但它最终执行了(在一定条件下或某个时刻)
    • 常用的回调函数
      • dom事件回调函数
      • 定时器回调函数
      • ajax请求回调函数
      • 生命周期回调函数
  • 函数中的this

    • 显式指定谁:obj.xxx()
    • 通过call/apply指定谁调用: xxx.call(obj)
    • 不指定谁调用: xxx(): window
    • 回调函数: 看背后是通过谁来调用的: window/其它

函数高级

原型与原型链

  1. 函数的prototype属性

    • 每个函数都有一个prototype属性,
      它默认指向一个Object空对象(即称为: 原型对象)
    • 原型对象中有一个属性constructor, 它指向函数对象
  2. 给原型对象添加属性(一般都是方法)

    • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
  • 所有函数都有一个特别的属性:

    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:

    • __proto__ : 隐式原型属性
  • 显式原型与隐式原型的关系

    • 函数的prototype: 定义函数时被自动赋值, 值默认为{},
      即用为原型对象
    • 实例对象的proto: 在创建实例对象时被自动添加,
      并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
    • 例子:
  • 原型链

    • 访问一个对象的属性时,
      • 先在自身属性中查找,找到返回
      • 如果没有, 再沿着proto这条链向上查找, 找到返回
      • 如果最终没找到,
        返回undefined(下图中函数列表的尽头proto=null就表示没找到返回undefined)
    • 别名: 隐式原型链
    • 作用: 查找对象的属性(方法)
    • 所有的实例对象都有proto属性, 它指向的就是原型对象
    • 这样通过proto属性就形成了一个链的结构—->原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
    • 函数的prototype属性指向实例的proto属性
    • 所有的函数都是 Function的实例(包括Function)
  • instanceof是如何判断的?

    • 表达式: A instanceof B
    • 如果B函数的显式原型对象在A对象的原型链上, 返回true,
      否则返回false
  • 测试题

    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
        原型测试题:
    <script>
    /*
    测试题1
    */
    var A = function() {

    }
    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
    */
    var F = function(){};
    Object.prototype.a = function(){
    console.log('a()')
    };
    Function.prototype.b = function(){
    console.log('b()')
    };
    var f = new F();
    f.a()
    f.b()
    F.a()
    F.b()
    //a()
    // f.b is not a function
    //a()
    //b()
    </script>

执行上下文与执行上下文栈

  • 变量提升与函数提升
    • 变量提升: 在变量定义语句之前,
      就可以访问到这个变量(undefined),只是还没有赋值
    • 函数提升: 在函数定义语句之前, 就可执行该函数
    • 先有变量提升, 再有函数提升
  1. 执行上下文
  • 代码分类(位置)
    • 全局代码
    • 函数代码
  • 全局执行上下文
    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var定义的全局变量==>undefined, 添加为window的属性
      • function声明的全局函数==>赋值(fun), 添加为window的方法
      • this==>赋值(window)
    • 开始执行全局代码
  • 函数执行上下文
    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
    • 对局部数据进行预处理
      • 形参变量==>赋值(实参)==>添加为执行上下文的属性
      • arguments==>赋值(实参列表), 添加为执行上下文的属性
      • var定义的局部变量==>undefined, 添加为执行上下文的属性
      • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
      • this==>赋值(调用函数的对象)
    • 开始执行函数体代码
  1. 执行上下文栈
  • 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象

  • 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)

  • 在函数执行上下文创建后, 将其添加到栈中(压栈)

  • 在当前函数执行完后,将栈顶的对象移除(出栈)

  • 当所有的代码执行完后, 栈中只剩下window

  • 例子:

  • 测试题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    执行上下文栈测试题:

    <script type="text/javascript">
    console.log('global begin: '+ i)
    var i = 1
    foo(1);
    function foo(i) {
    if (i == 4) {
    return;
    }
    console.log('foo() begin:' + i);
    foo(i + 1);//递归调用:在函数内部调用自己,一般会设置条件来终止函数
    console.log('foo() end:' + i);
    }
    console.log('global end: ' + i)
    // global begin: undefined
    // foo() begin:1 由于递归输出1.2.3
    // foo() begin:2
    // foo() begin:3
    // foo() end:3 因为foo begin执行完后要将栈顶的对象移除出栈
    // foo() end:2
    // foo() end:1
    // global end: 1
    </script>
    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
    <script type="text/javascript">
    /*
    测试题1: 先预处理变量, 后预处理函数
    */
    function a() {}
    var a;
    console.log(typeof a)//function


    /*
    测试题2: 变量预处理, in操作符
    */
    if (!(b in window)) {
    var b = 1;
    }
    console.log(b)//undefined

    /*
    测试题3: 预处理, 顺序执行
    */
    var c = 1//var c;
    function c(c) {
    console.log(c)
    var c = 3
    }
    //c=1
    c(2)//报错 会把c识别成变量而不是函数

    </script>

作用域与作用域链

  • 理解:

    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链:
      • 多个上下级关系的作用域形成的链,
        它的方向是从下向上的(从内到外)
      • 查找变量时就是沿着作用域链来查找的
  • 分类:

    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用

    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文

    1. 区别1

      • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
      • 全局执行上下文环境是在全局作用域确定之后,
        js代码马上执行之前创建
      • 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
    2. 区别2

      • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
      • 上下文环境是动态的, 调用函数时创建,
        函数调用结束时上下文环境就会被释放
    3. 联系

      • 上下文环境(对象)是从属于所在的作用域
      • 全局上下文环境==>全局作用域
      • 函数上下文环境==>对应的函数使用域
  • 测试题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script type="text/javascript">
    /*
    问题: 结果输出多少?
    */
    var x = 10;
    function fn() {
    console.log(x);
    }
    function show(f) {
    var x = 20;
    f();
    }
    show(fn);
    //10
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script type="text/javascript">
    /*
    说说它们的输出情况
    */

    var fn = function () {
    console.log(fn)
    }
    fn()//函数作用域找不到,去外部作用域能找到fn
    //ƒ () {
    // console.log(fn)
    //}

    var obj = {
    fn2: function () {
    console.log(fn2)
    }
    }
    obj.fn2()//函数作用域找不到,全局作用域也找不到(只有obj,没有fn2);修改为this.fn2或obj.fn2为正确
    </script>

闭包

  • 理解

    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,
      就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象,
      这个对象中包含引用的变量属性
    • 闭包的数量取决于调用外部函数的次数,只有调用外部函数的时候才会创建内部函数对象
  • 作用

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

    1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
    2. 死亡: 在嵌套的内部函数成为垃圾对象时
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
          <script type="text/javascript">
      function fun1() {
      //此处闭包已经产生(函数提升,内部函数对象已经创建了)
      var a = 3;
      function fun2() {
      a++;
      console.log(a);
      }
      return fun2;
      }
      var f = fun1();
      f();
      f();
      f = null //此时闭包对象死亡
      </script>
  • 应用

  1. 定义JS模块
    • 具有特定功能的js文件
    • 将所有的数据和功能都封装在一个函数内部(私有的)
    • 只向外暴露一个包含n个方法的对象或函数
    • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
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

//Module.js

//自定义模块1
function coolModule() {
//私有的数据
var msg = 'atguigu'
var names = ['I', 'Love', 'you']

//私有的操作数据的函数
function doSomething() {
console.log(msg.toUpperCase())
}
function doOtherthing() {
console.log(names.join(' '))
}

//向外暴露包含多个方法的对象,也可以用return(还需在调用时创建对象)
window.module {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
//调用闭包
<script type="text/javascript" src="Module.js"></script>//引入
<script type="text/javascript">
module.doSomething()
module.doOtherthing()
</script>
  • 缺点及解决方案
  1. 缺点

    • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
    • 容易造成内存泄露
  2. 解决

    • 能不用闭包就不用
    • 及时释放: f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出

    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  2. 内存泄露

    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:
      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包

面向对象高级

对象的创建模式

  • Object构造函数模式

    • 套路: 先创建空Object对象, 再动态添加属性/方法
    • 适用场景: 起始时不确定对象内部数据
    • 问题: 语句太多
      1
      2
      3
      var obj = {};
      obj.name = 'Tom'
      obj.setName = function(name){this.name=name}
  • 对象字面量模式

    • 套路: 使用{}创建对象, 同时指定属性/方法
    • 适用场景: 起始时对象内部数据是确定的
    • 问题: 如果创建多个对象, 有重复代码
      1
      2
      3
      4
      var obj = {
      name : 'Tom',
      setName : function(name){this.name = name}
      }
  • 工厂模式

    • 套路: 通过工厂函数动态创建对象并返回
    • 适用场景: 需要创建多个对象
    • 问题: 对象没有一个具体的类型, 都是Object类型
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
               // 工厂函数: 返回一个需要的数据的函数
      function createPerson(name, age) {
      var p = {
      name: name,
      age: age,
      setName: function (name) {
      this.name = name
      }
      }
      return p
      }
      var p1=createPerson(name, age);
      ```
      * 构造函数模式
      * 套路: 自定义构造函数, 通过new创建对象
      * 适用场景: 需要创建多个类型确定的对象
      * 问题: 每个对象都有相同的数据, 浪费内存
      function Person(name, age) {
      this.name = name;
      this.age = age;
      this.setName = function(name){this.name=name;};
      }
      new Person(‘tom’, 12);
      1
      2
      3
      4
      -   构造函数+原型的组合模式

      - 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
      - 适用场景: 需要创建多个类型确定的对象
      function Person(name, age) {
      this.name = name;
      this.age = age;
      }
      Person.prototype.setName = function(name){this.name=name;};
      new Person(‘tom’, 12);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      ## 继承模式

      - **原型链继承** : 得到方法

      - 套路
      1. 定义父类型构造函数
      2. 给父类型的原型添加方法
      3. 定义子类型的构造函数
      4. 创建父类型的对象赋值给子类型的原型
      5. 将子类型原型的构造属性设置为子类型
      6. 给子类型原型添加方法
      7. 创建子类型的对象: 可以调用父类型的方法
      - 关键
      1. 子类型的原型为父类型的一个实例对象
      function Parent(){}
      Parent.prototype.test = function(){};
      function Child(){}
      Child.prototype = new Parent(); // 子类型的原型指向父类型实例(让子类的原型成为父类的实例对象)
      Child.prototype.constructor = Child//让子类型的原型的constructor指向子类型
      var child = new Child(); //有test()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      - 例子
      ![](/JS高级/原型链继承.png)
      - 借用构造函数 : 得到属性

      - 套路:
      1. 定义父类型构造函数
      2. 定义子类型构造函数
      3. 在子类型构造函数中调用父类型构造
      - 关键:
      1. 在子类型构造函数中通用super()调用父类型构造函数
      function Parent(xxx){this.xxx = xxx}
      Parent.prototype.test = function(){};
      function Child(xxx,yyy){
      Parent.call(this, xxx);//借用构造函数  this.Parent(xxx)不能这样写,但是意思相同
      }
      var child = new Child(‘a’, ‘b’); //child.xxx为’a’, 但child没有test()
      1
      2
      3
      4
      -   原型链+借用构造函数的组合继承

      1. 利用原型链实现对父类型对象的方法继承
      2. 利用call()借用父类型构建函数初始化相同属性
      function Parent(xxx){this.xxx = xxx}
      Parent.prototype.test = function(){};
      function Child(xxx,yyy){
      Parent.call(this, xxx);//借用构造函数 this.Parent(xxx)
      }
      Child.prototype = new Parent(); //得到test()
      var child = new Child(); //child.xxx为’a’, 也有test()
      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
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      -   new一个对象背后做了些什么?

      - 创建一个空对象
      - 给对象设置**proto**, 值为构造函数对象的prototype属性值
      this.**proto** = Fn.prototype
      - 执行构造函数体(给对象添加属性/方法)

      # 线程机制与事件机制

      ## 线程与进程

      - 进程:
      - 程序的一次执行, 它占有一片独有的内存空间
      - 可以通过windows任务管理器查看进程
      - 线程:
      - 是进程内的一个独立执行单元
      - 是程序执行的一个完整流程
      - 是CPU的最小的调度单元
      - 关系
      - 程序是在某个进程中的某个线程执行的
      - 一个进程中一般至少有一个运行的线程: 主线程
      - 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
      - 一个进程内的数据可以供其中的多个线程直接共享
      - 多个进程之间的数据是不能直接共享的
      - 线程池:保存多个线程对象的容器,实现线程对象的反复利用

      ## 浏览器内核模块组成

      1. 什么是浏览器内核?

      - 支持浏览器运行的最核心的程序

      2. 不同的浏览器可能不太一样

      - Chrome, Safari: webkit
      - firefox: Gecko
      - IE: Trident
      - 360,搜狗等国内浏览器: Trident + webkit

      3. 内核由很多模块组成

      - 主线程
      - js引擎模块 : 负责js程序的编译与运行
      - html,css文档解析模块 : 负责页面文本的解析
      - DOM/CSS模块 : 负责dom/css在内存中的相关处理
      - 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
      - 分线程
      - 定时器模块 : 负责定时器的管理
      - DOM事件模块 : 负责事件的管理
      - 网络请求模块 : 负责Ajax请求

      ## 定时器问题

      1. 定时器真是定时执行的吗?
      - 定时器并不能保证真正定时执行
      - 一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)

      2. 定时器回调函数是在分线程执行的吗?
      - 在主线程执行的, js是单线程的

      3. 定时器是如何实现的?
      - 事件循环模型

      ## js线程

      - js是单线程执行的(回调函数也是在主线程)
      - H5提出了实现多线程的方案: Web Workers
      - 只能是主线程更新界面

      1. 如何证明js执行是单线程的?

      - setTimeout()的回调函数是在主线程执行的
      - 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

      2. 为什么js要用单线程模式, 而不用多线程模式?

      - JavaScript的单线程,与它的用途有关。
      - 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
      - 这决定了它只能是单线程,否则会带来很复杂的同步问题

      3. 代码的分类:

      - 初始化代码
      - 回调代码

      4. js引擎执行代码的基本流程

      - 先执行初始化代码: 包含一些特别的代码
      - 设置定时器
      - 绑定监听
      - 发送ajax请求
      - 后面在某个时刻才会执行回调代码

      ## 事件循环模型

      1. 所有代码分类
      - 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器,
      发送ajax请求的代码
      - 回调执行代码(异步代码): 处理回调逻辑

      2. js引擎执行代码的基本流程:
      - 初始化代码===\>回调代码

      3. 模型的2个重要组成部分:
      - 事件管理模块
      - 回调队列

      4. 模型的运转流程
      - 执行初始化代码, 将事件回调函数交给对应模块管理
      - 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
      - 只有当初始化代码执行完后(可能要一定时间),
      才会遍历读取回调队列中的回调函数执行\
      ![](/JS高级/事件循环模型.png)

      ## H5-Web-Workers-多线程

      1. H5规范提供了js分线程的实现, 取名为: Web Workers

      - Worker
      var worker = new Worker(‘worker.js’);
      worker.onMessage = function(event){event.data} : 用来接收另一个线程发送过来的数据的回调//用来绑定消息监听(主线程接收分线程返回的数据)
      worker.postMessage(data1) : 主线程向分线程发送数据
  1. 相关API

    • Worker: 构造函数, 加载分线程执行的js文件
    • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
    • Worker.prototype.postMessage: 向另一个线程发送消息
  2. 不足

    • worker内代码不能操作DOM(更新UI)
    • 不能跨域加载JS
    • 不是每个浏览器都支持这个新特性