0%

JS高级

基础深入

数据类型

JS 中的数据类型分两大类

  • 基本(值)类型
  • 对象(引用)类型

基本(值)类型

String —— 任意字符串
Number —— 任意数字, 包括 NaN
Boolean —— true / false
Undefined —— undefined
Null —— null

引用(对象)类型

Object —— 任意对象都是 Object 类型的实例

  • Function —— 一种特别的对象 (可以执行)
  • Array —— 一种特别的对象(有数值下标, 内部数据是有序的)

判断数据类型

typeof

  • 返回数据类型的 字符串
    • 判断 数值 / 字符串 / 布尔值 / undefined / 函数
    • 不能判断 null
    • 不能区分 object 和 array
      1
      2
      3
      4
      5
      typeof a

      typeof null // 'object'

      typeof fun // 'function'

instanceof

  • 返回 true / false
    • 专门用来 判断对象 的具体类型的, 是不是 数组 / 普通对象 / 函数对象
      1
      2
      3
      obj instanceof Object(一个构造函数名) 

      fun instanceof Function

===

  • 返回 true / false
    • 可以判断 undefined / null
      1
      2
      var a = null
      console.log(a === null) // true

基本问题?

  1. undefined 和 null 的区别?

    • undefined 代表声明了但是没有赋值
    • null 代表赋值了, 值为 null
  2. 什么时候赋值为 null?

    • 初始赋值为 null, 表明将要赋值为对象;
    • 最后赋值为 null, 让对象成为垃圾对象(被回收)
  3. 严格区分变量类型和数据类型?

    • 数据类型
      • 基本数据类型
      • 对象数据类型
    • 变量类型(变量内存值的类型)
      • 基本类型 —— 保存的就是基本数据类型的数据
      • 引用类型 —— 保存的是对象的地址值

数据 ? 内存 ? 变量 ?

数据

存储在内存中, 代表特定信息的, 本质上是 0101……

  • 数据的特点
    • 可传递
    • 可运算

内存

可存储数据的空间(临时的)

  • 内存的分类
    • 栈内存
      • 全局变量和局部变量
    • 堆内存
      • 对象内容

变量

可变化的量, 由变量名和变量值组成

  • 每个变量都对应一块小内存
  • 变量名用来查找对应的内存; 变量值就是内存中保存的数据

内存、数据、变量三者之间的关系

  • 数据
    • 存储在内存中代表特定信息的东西, 本质是 01010...
  • 内存
    • 用来存储数据的空间(临时的)
  • 变量
    • 内存的标识

几个问题?

  • 赋值和内存的问题?
    • var a = xx, a 内存中到底保存的是什么?
      • 如果 xx 是基本数据, 保存的就是这个数据
      • 如果 xx 是对象, 保存的是这个对象的地址值
      • 如果 xx 是一个变量, 保存的就是 xx 的内存内容(可能是基本数据, 也可能是地址值)
  • 引用变量赋值的问题?
    • n 个引用变量指向同一个对象, 通过一个变量修改对象内部数据, 其他所有变量看到的是修改之后的数据
  • 在 JS 调用函数时传递参数变量时, 是值传递还是引用传递?
    • 可能是值传递, 也可能是引用传递(地址值)
  • JS 引擎如何管理内存?
    • 内存生命周期
      • 分配内存空间, 得到它的使用权
      • 存储数据
      • 释放当前内存空间
    • 释放内存
      • 局部变量 —— 函数执行完自动释放
      • 对象 —— 成为垃圾对象, 再由垃圾回收器回收
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // 常见面试题
        var a = { age: 12 }

        function fn (obj) {
        obj = { age: 15 }
        }

        fn(a) // 调用 fn 函数时, 相当于执行了 obj = a; obj = { age: 15 }; a 并没有被改变
        console.log(a.age) // 12

对象

  • 什么是对象?

    • 一种复合的数据类型, 多个数据的封装体
    • 用来保存多个数据的容器
    • 一个对象代表现实中的一个事物
  • 为什么要用对象?

    • 统一管理多个数据
  • 对象的组成?

    • 属性 —— 属性名(字符串) 和 属性值(任意类型) 组成
    • 方法 —— 一种特别的属性, 属性值是函数
  • 如何访问对象内部数据?

    • 对象.属性名

      • 编码简单, 有时不能用
    • 对象[属性名字符串]

      • 能通用
      • 如果是 常量属性名一定要加引号
      • 如果是变量属性名则不能加引号
        1
        2
        3
        4
        5
        6
        7
        8
        var a = { name: 'zs' }

        a.name // 'zs'
        a['name'] // 'zs'
        a[name] // undefined

        var name = 'name'
        a[name] // 'zs'
  • 什么时候必须使用 对象[属性名字符串] 的方式?

    • 1.属性名包含特殊字符(-、空格等)
    • 2.属性名不确定 —— 对象[变量名 ]

函数

  • 什么是函数?

    • 实现特定功能的代码段
    • 只有函数是可以执行的, 其他类型的数据不能执行
  • 为什么要使用函数?

    • 提高代码复用
    • 便于阅读交流
  • 如何定义函数?

    • 函数声明的方式
    • 函数表达式的方式
  • 如何调用(执行)函数?

    • 函数名() —— 直接调用
    • obj.fun() —— 通过对象调用
    • new Fun() —— new 调用
    • fun.call/apply(obj) —— 类似于 obj.fun(), 但是这 只是临时的使 fun 成为 obj 的方法
      可以让一个函数成为任意指定对象的方法进行调用
      1
      2
      3
      4
      5
      6
      7
      var obj = {}
      function test() {
      this.xx = 'hello'
      }
      obj.test() // undefined is not a function
      test.call(obj)
      console.log(obj.xx) // 'hello'

回调函数

  • 什么函数才是回调函数?

    1. 你定义的
    2. 你没有调
    3. 但最终它执行了
  • 常见的回调函数?

    1. dom 事件回调函数
    2. 定时器回调函数
    3. ajax 请求回调函数
    4. 生命周期回调函数

IIFE

  • Immediately- Invoked Function Expression

    • 匿名函数自调用
      1
      2
      3
      4
      // 匿名函数自调用
      (function () {
      console.log('haha')
      })()
  • 作用

    • 隐藏实现
    • 不会污染全局命名空间
    • 可以用来编写 js 模块

函数中的 this

  • this 是什么?
    • 函数本质都是通过某个对象来调用的, 所有函数内部都有一个变量 this
    • 它的值是 调用函数的当前对象
  • 如何确定 this 的值?
    • fun() —— window
    • p.fun() —— p
    • new Person() —— 新创建的实例对象
    • p.call(obj) —— obj

bind()

用来改变函数内部的 this
返回值 —— 改变 this 指向后的函数(可以预置参数)

1
2
3
4
5
6
7
8
function fn (a, b, c) {
console.log(this)
console.log(a, b, c)
}

var newFn = fn.bind(obj, 1) // 预传参 1

newFn(2, 3) // obj 1, 2, 3

总结:调用 bind 后的函数还是在调用原函数,只是改变了原函数的 this 而已

函数高级

原型与原型链

原型

每个函数都有一个 prototype 属性, 它默认指向一个 Object 空对象 (即称为原型对象)

  • 原型对象中有一个属性 constructor, 它指向函数对象
  • 给原型对象添加属性(一般是方法)
    • 作用 —— 实例对象自动拥有原型中的属性(方法)

显示原型与隐式原型

每个函数都有一个 prototype, 即显式原型(属性)
每个实例对象都有一个 __proto__, 可称为隐式原型(属性)

对象的隐式原型的值为其对应构造函数的显式原型的值

  • 总结:
    • 函数的 prototype 属性 —— 在定义函数时自动添加的, 默认值是一个空 Object 对象
    • 对象的 __proto__ 属性 —— 创建对象时自动添加的, 默认值为其对应的构造函数的 prototype 属性值
    • 程序员能直接操作 显示原型, 不能直接操作隐式原型(ES6 之前)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function Fun() {

      }

      var fun = new Fun()

      Fun.prototype.test = function () {

      }

      console.log(fun.__proto__ === Fun.prototype) // true
      fun.test()

原型链

  • 访问一个对象的属性时,
    • 先在自身中查找, 找到返回
    • 如果自身没有, 再沿着 __proto__ 这条链向上查找, 找到返回
    • 如果最终没找到, 返回 undefined
  • 别名 —— 隐式原型链
  • 作用 —— 查找对象的属性(方法)

原型的继承

  • 构造函数的实例对象自动拥有构造函数的原型对象的属性(方法)
    利用的就是 原型链

注意

  • 函数的显式原型指向的对象默认是空 Object 实例对象(但 Object 不满足)

    1
    console.log(Object.prototype instanceof Object) // false
  • 所有的函数都是 Function 的实例 (包含 Function)

    1
    console.log(Function.__proto__ === Function.prototype) // true
  • Object 的原型对象是原型链的尽头

    1
    console.log(Object.prototype.__proto__) // null

原型链属性问题

  • 读取对象的属性值时, 会自动到原型链中查找
  • 设置对象的属性值时, 不会查找原型链, 如果当前对象自身没有此属性, 直接添加此属性并设置其值
  • 方法一般定义在原型上, 属性一般通过构造函数定义在对象自身上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Fn () {

    }
    Fn.prototype.a = 'xx'

    var fn1 = new Fn()
    var fn2 = new Fn()
    fn2.a = 'yy' // 设置对象的属性值, 不会查找原型链

    console.log(fn1.a, fn2.a) // 'xx' 'yy'

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

变量提升和函数提升

  • 变量提升

    • 使用 var 声明的 变量, 在定义语句之前就可以访问到
    • 值 —— undefined
      1
      2
      3
      4
      if(!(b in window)) {
      var b = 1 // 声明提前
      }
      console.log(b) // undefined
  • 函数声明提升

    • **通过 function 声明的 **函数, 在声明之前就可以直接调用
    • 函数表达式方式不会提升
  • 变量提升和函数提升是如何产生的?

    • 执行上下文预处理
  • ⚠️先执行变量提升

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function a () {}
    var a
    console.log(typeof a) // 'function'


    var c = 1
    function c(c) {
    console.log(c)
    var c = 3
    }
    c(2) // c is not a function

    // 相当于
    var c
    function c(c) {...}
    c = 1
    c(2)

执行上下文

  • 全局执行上下文

    • 执行全局代码前, 将 window 确定为全局执行上下文
    • 对全局数据进行预处理
      • var 定义的全局变量, 添加为 window 的属性(值为 undefined)
      • function 声明的函数, 添加为 window 的方法
      • this 设置为 window
    • 开始执行全局代码
  • 函数执行上下文

    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
    • 对局部数据进行预处理
      • var 定义的局部变量, 添加为执行上下文的属性(值为 undefined)
      • function 声明的函数, 添加为执行上下文的方法
      • 形参被赋值为实参值, 添加为执行上下文的属性
      • arguments 赋值为实参列表, 添加为执行上下文的属性
      • this 赋值为 调用函数的对象
    • 开始执行函数体

执行上下文栈

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

作用域与作用域链

作用域

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

作用域与执行上下文

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

作用域链

  • 多个上下级函数的作用域形成的链, 它的方向是从下向上 (从内向外) 的
  • 查找变量 时就是沿着作用域链来查找的 (查找对象的属性是沿着原型链来查找的)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var x = 10
    function fn() {
    console.log(x)
    }
    function show(f) {
    var x = 20
    f()
    }
    show(fn) // 10 作用域在函数声明时候就确定了

    var fn = function () {
    console.log(fn)
    }
    fn() // function () { console.log(fn) }

    var obj = {
    fn2: function () {
    console.log(fn2)
    }
    }
    obj.fn2() // fn2 is not defined

闭包closure

  1. 如何产生闭包?

    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  2. 闭包到底是什么?

    • 理解 1: 闭包是嵌套的内部函数
    • 理解 2: 包含被引用变量(或函数)的对象 - closure
    • ⚠️ 闭包存在于嵌套的内部函数中
  3. 产生闭包的条件?

    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量 / 函数)

常见的闭包

  1. 将函数作为另一个函数的返回值
  2. 将函数作为实参传递给另一个函数调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 例 1
    function fn1() {
    var a = 2
    function fn2 () { // 执行内部函数定义时,闭包产生
    a++
    console.log(a)
    }
    return fn2
    }
    var f = fn1()
    f() // 3 在外部操作内部变量 a
    f() // 4

    // 例 2
    function showDelay(msg, time) {
    setTimeout(function () {
    alert(msg)
    }, time)
    }
    showDelay('hello', 2000)

闭包的作用

  1. 使用函数内部的变量在函数执行完毕后, 仍然存活在内存中 (延长了局部变量的生命周期)
  2. 让函数外部可以操作(读 / 写)到函数内部的数据(变量 / 函数)
  3. 问题?
    • 函数执行完后, 函数内部声明的局部变量是否还存在?
      • 一般不存在, 存在于闭包中的变量有可能存在 (见 例 1)
    • 在函数外部能直接访问函数内部的局部变量吗?
      • 不能, 但是通过闭包可以让外部操作它 (见 例 1)

闭包的生命周期

产生

  • 嵌套的内部函数定义时就产生了(不是在被调用时)
  • 外部函数调用几次就产生几个闭包

死亡

  • 在嵌套的内部函数成为垃圾对象时 (不再有变量去访问它时)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function fn1() {
    var a = 2
    function fn2 () { // 执行内部函数定义时,闭包产生
    a++
    console.log(a)
    }
    return fn2
    }
    var f = fn1()
    f() // 3 在外部操作内部变量 a
    f() // 4

    f = null // 不再有变量可以直接访问到 fn2 了, 闭包死亡(fn2 在被 js 回收前还存在内存中)

闭包的应用

定义 JS 模块

  • 具有特定功能的 JS 文件
    • 将所有的数据和功能都封装在一个函数内部(私有的)
    • 只向外暴露一个包含 n 个属性(方法)的对象或函数
    • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // myModule.js
      (function myModule() {
      var msg = 'hEllo'
      function doSomething() {
      console.log(msg.toUpperCase())
      }
      function doOtherthing() {
      console.log(msg.toLowerCase())
      }
      window.myModule = {
      doSomething: doSomething,
      doOtherthing: doOtherthing
      }
      })()
1
2
3
4
5
6
// html 文件
<script src='myModule.js'></script>
<script>
myModule.doSomething()
myModule.doOtherthing()
</script>

闭包的缺点

缺点

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

解决

  • 能不用闭包就不用
  • 及时释放 —— 让引用的函数成为垃圾对象 (回收闭包)

内存溢出和内存泄漏

内存溢出

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

内存泄漏

  • 占用的内存没有及时释放
  • 内存泄漏积累多了就容易导致内存溢出
  • 常见的内存泄漏
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      // 意外的全局变量
      function () {
      a = new Array(10000000)
      console.log(a)
      }

      // 没有及时清理的计时器
      setInterval(function () {
      console.log('aaaa')
      }, 1000)

      // 闭包
      function fn1 () {
      var a = 1
      function fn2 () {
      a++
      console.log(a)
      }
      return fn2
      }
      var f = fn1()
      f()

面向对象高级

对象创建方式

  • 方式一 —— Object 构造函数模式

    • 套路: 先创建空 Object 对象, 再动态添加属性 / 方法
    • 适用场景: 起始不确定对象内部数据
    • 问题: 语句太多
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var p1 = new Object()
      p1.name = '章三'
      p1.age = 18
      p1.setName = function (name) {
      this.name = name
      }

      var p2 = new Object()
      p2.name = '里斯'
      p2.age = 22
      p2.setName = function (name) {
      this.name = name
      }
  • 方式二 —— 字面量模式 (常用)

    • 套路: 使用 {} 创建对象, 同时指定内部属性
    • 适用场景: 起始时对象内部数据是确定的
    • 问题: 如果创建多个对象, 有重复代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      var p1 = {
      name: '章三',
      age: 18,
      setName: function (name) {
      this.name = name
      }
      }

      var p2 = {
      name: '里斯',
      age: 22,
      setName: function (name) {
      this.name = name
      }
      }
  • 方式三 —— 工厂模式 (返回一个对象的函数)

    • 套路: 通过工厂函数动态创建对象并返回
    • 适用场景: 需要创建多个对象
    • 问题:对象没有一个具体的类型, 都是 Object 类型
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function createPerson(name, age) {
      var obj = {
      name: name,
      age: age,
      setName: function (name) {
      this.name = name
      }
      }
      return obj // 返回一个对象的函数 —— 工厂函数
      }
      var p1 = createPerson('Tom', 16) // p1、p2 都是 Object 类型
      var p2 = createPerson('Bob', 18)
  • 方式四 —— 自定义构造函数模式

    • 套路: 自定义构造函数, 通过 new 创建对象
    • 适用场景: 需要创建多个类型确定的对象
    • 问题: 每个对象都有相同的数据(主要指方法), 浪费内存
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person(name, age) {
      this.name = name
      this.age = age
      this.setName = function (name) {
      this.name = name
      }
      }
      var p1 = new Person('Tom', 16)
      var p2 = new Person('Bob', 18)
  • 方式五 —— 自定义构造函数 + 原型模式(常用)

    • 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
    • 适用场景: 需要创建多个类型确定的对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person(name, age) { // 在构造函数中,只初始化一般属性
      this.name = name
      this.age = age
      }
      Person.prototype.setName = function (name) {
      this.name = name
      }
      var p1 = new Person('Tom', 17)
      var p2 = new Person('Bob', 20)

原型链的继承

  • 原理
    • 子类型的原型为父类型的一个实例
      1
      2
      Son.prototype = new Father() // 为了能看到父类型的方法
      Son.prototype.constructor = Son // 修正 constructor 属性

线程机制与事件机制

进程与线程

  • 进程
    • 程序的一次运行, 它占用独有的一块内存空间
    • 可以通过 windows 任务管理器查看
  • 线程
    • 是进程内一个独立的执行单元
    • 是程序执行的一个完整流程
    • 是 CPU 的最小调度单元
  • 多线程优缺点
    • 优点
      • 能有效提升 CPU 的利用率
    • 缺点
      • 创建多线程开销
      • 线程间切换开销
      • 死锁与状态同步问题
  • JS 是单线程运行的
    • js 引擎执行代码的基本流程
      • 先执行初始化代码
        • 设置定时器
        • 绑定监听
        • 发送 ajax 请求
      • 后面某个时刻才会执行回调代码
    • H5 中的 Web Workers 可以多线程运行
  • 为什么 js 是单线程的?
    • 与它的用途有关
    • 作为浏览器脚本语言, js 的主要用途是与用户互动, 以及操作 DOM
    • 这决定了它只能是单线程, 否则会带来很复杂的问题 (同时操作会有冲突)

浏览器内核

  • 支撑浏览器运行的最核心的程序
  • 内核由很多模块组成
    • 主线程
      • js 引擎模块 —— 负责 js 程序的编译和运行
      • html/css 文档解析模块 —— 负责页面文本的解析
      • DOM/CSS 模块 —— 负责 dom/css 在内存中的相关处理
      • 布局与渲染模块 —— 负责页面的布局和效果显示
    • 分线程
      • 定时器模块 —— 管理定时器
      • DOM 事件模块 —— 管理事件
      • 网络请求模块 —— 负责 ajax 请求
  • Chrome / Safari —— webkit
  • Firefox —— Gecko
  • IE —— Trident

定时器

  • 定时器是在主线程执行的 (js 是单线程运行的)
  • 定时器是如何实现的?
    • 事件循环模型

事件循环模型

  • 代码分类
    • 初始化执行代码 —— 包含 dom 监听、设置定时器、发送 ajax 请求
    • 回调执行代码 —— 处理回调逻辑
  • js 引擎执行代码的基本流程
    • 初始化代码 >>> 回调代码
  • 模型的两个组成部分
    • 事件管理模块
    • 回调队列
  • 模型的运转流程
    • 执行初始化代码时, 将事件回调函数交给对应的模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调队列中
    • 只有当初始化代码执行完后, 才会遍历读取回调队列中的回调函数执行

H5 Web Workers (多线程)

⚠️ 用的少, 但面试会问到

  • H5 规范提供了 js 多线程的实现, 叫 web workers

    • Web Workers 是 html5 提供的一个 js 多线程解决方案
    • 我们可以将一些大计算量的代码交由 Web Workers 运行而不冻结用户界面
      但是子线程完全受主线程控制, 且不得操作 DOM
    • 所以, 这个新标准并没有改变 js 单线程的本质
  • 相关 API

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

    • worker 内代码不能操作 DOM
      • 看不到 window, 分线程内的全局对象不是 window, 不能调用 window 的方法, 所以不能更新界面
    • 不能跨域加载 JS
    • 不是每个浏览器都支持
    • 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
      // 主线程代码
      var input = document.getElementById('number')

      document.getElementById('btn').onclick = function () {
      var number = input.value

      // 创建一个 worker
      var worker = new Worker('worker.js') // 参数是相对路径

      // 绑定接收消息的监听
      worker.onmessage = function (event) {
      console.log('主线程接收分线程返回的数据:' + event.data)
      alert(event.data)
      }

      // 向分线程发送消息
      worker.postmessage(number)
      console.log('主线程向分线程发送数据:' + number)
      }

      // worker.js
      function fibonacci (n) {
      return n <=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)
      }
      console.log (this) // 不是 window
      var onmessage = function (event) {
      console.log('分线程接收到主线程数据:' + event.data)
      var number = event.data // 通过 event.data 获取发送过来的数据
      postMessage(fibonacci(number)) // 将获取到的数据发给主线程
      }