Javascript

目录

  • 第一章、基础总结
      1. 数据类型(字面量)
        1. 分类
        1. 判断
        1. 相关问题
      1. 数据·变量·内存
        1. 类型转换
        1. 运算符
        1. Unicode 编码
        1. 条件运算符(三元运算符)
        1. 流程控制语句
        1. 相关问题
      1. 对象
        1. 对象的分类
        1. 基本和引用数据类型
        1. 对象的相关问题
      1. 函数
        1. 返回值的类型
        1. 枚举对象
        1. 全局作用域
        1. IIFE
        1. 声明提前
        1. 函数作用域
        1. 函数的对象 call()/apply()
        1. 构造函数
        1. 构造中的 this
        1. 回调函数的相关问题
        1. 函数的相关问题
      1. Math
      1. 数组
      1. BOM
      1. JSON
  • 第二章、函数高级
      1. 闭包
        1. 认识闭包
        1. 常见的闭包
        1. 闭包的作用
        1. 闭包的生命周期
        1. 闭包的缺点及解决
      1. 原型
        1. 认识原型
        1. 显示原型与隐式原型
        1. 原型链
        1. 原型链的属性
        1. 探索 instanceof
      1. 执行上下文和执行上下文栈
        1. (变量/函数)声明提前
        1. 执行上下文
        1. 执行上下文栈
        1. 相关面试题
      1. 作用域
        1. 认识作用域
        1. 作用域与执行上下文
      1. 内存溢出与内存泄露
  • 第三章、对象高级
      1. 对象创建模式
        1. Object 构造函数模式
        1. 对象字面量模式
        1. 工厂模式
        1. 自定义构造函数模式
        1. 构造函数+原型的组合模式
      1. 继承模式
        1. 原型链继承
        1. 组合继承(原型链+构造函数)
  • 第四章、线程机制和事件
      1. 浏览器的时间循环(轮询)
      1. Web Worker 测试

第一章 基础总结

1. 数据类型(字面量)

1.1 分类
  • 基本(值)类型
    • String => 任意字符串
    • Number => 任意的数字
    • Boolean => true/false
    • Null => null
    • Undefined => undefined
  • 对象(引用)类型
    • Object => 任意对象
    • Function => 一种特别的对象(可以执行)
    • Array => 一种特别的对象(数值下标,内部数据为有序的)
1.2 判断
  • typeof
    • 可以判断 undefined、数值、字符串、布尔值、function
    • 不能判断 Null 与 Object Object 与 Array
  • instanceof
    • 判断对象的具体类型
  • ===
    • 可以判断 undefined、null
1.3 相关问题

问题 1:undefined 与 null 的区别?

  1. undefined 代表定义未赋值、
  2. null 代表了定义了并已经赋值

问题 2:什么时候给变量赋值为 null?

  1. 初始赋值 => 表明将要赋值
  2. 结束赋值 => 让对象被回收

问题 3:严格要求变量的类型与数据类型?

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

2. 数据·数据·内存

2.1 类型转换
  • 转换成 String 类型
    • toString()
  • 转换成 Number 类型
    • parseInt()
    • parseFloat()
  • 转换成 Boolean 类型
    • 数字-->布尔
      • 除了 0 和 NaN,其余都是 true
    • 字符串-->布尔
      • 除了空串,其余都是 true
    • null 和 undefined 都会转换成 false
    • 对象转换成 true
2.2 运算符
  • 布尔值时
    • ! (非)
    • && (与) --> 一个为 false,全为 false
    • || (或) --> 一个为 true, 全为 true
  • 非布尔值时
    • && (与)
      • 如果第一个值为 true,则必然返回第二个值
      • 如果第一个值为 false,则直接返回第一个值
    • || (或)
      • 如果第一个值为 true,则直接返回第一个值
      • 如果第一个值为 false,则直接返回第二个值
  • 优先级: || 小于 &&
2.3 Unicode 编码
  • 控制台可以输出 \u000
  • 网页中可以输出 ⚀==> ⚀
2.4 条件运算符(三元运算符)
  • 条件表达式?语法 1:语法 2
    • [条件为 true 输出语法 1,条件为 false 输出语法 2]
2.5 流程控制语句
  • 分类

    • 条件判断语句
    • 条件分支语法
    • 村换语句
  • break 终止当次循环 终止内部循环
  • continue 跳过当前循环 继续从起始位置循环
  • 数值

Number.MAX_VALUE 表示数值的最大值
Number.MIN_VALUE 表示数值的最小值

2.6 相关问题

问题 1:什么是数据?

  1. 存储在内存中代表特定信息的(本质上就是 0100)
  2. 数据的特点:
    • 可传递
    • 可运算
  3. 一切皆数据
  4. 内存中所有操作的目标:数据
    • 算术运算
    • 逻辑运算
    • 赋值
    • 运行函数

问题 2:什么是内存?

  1. 内存条通电后产生的可存储数据的空间(临时的)
  2. 内存产生和死亡:
    • 内存条(电路板) ==> 通电 ==> 产生内存空间 ==> 存储数据 ==> 处理数据 ==> 断电 ==> 内存空间的数据消失
  3. 一块小内存的 2 个数据
    • 内存存储的数据
    • 地址值
  4. 内存分类
    • 栈: 全局变量/局部变量
    • 堆: 对象

问题 3:什么是变量?

  1. 可变化的量,由变量名和变量值组成
  2. 每个变量都有对应一个小内存
    • 变量名用来查找对应的内存
    • 变量值就是内存中保存的数据

问题 4:内存,数据,变量 三者之间的关系?

  1. 内存用来存储数据的空间
  2. 变量是内存的标识

问题 5: var a = xxx, a 内存中到底保存的是什么?

  1. xxx 是基本数据 --> 数据
  2. xxx 是一个对象 --> 对象的地址值
  3. xxx 是一个变量 --> xxx 的内容

问题 6: 关于引用变量赋值问题

  1. 2 个引用变量指向同一个对象
    • 通过一个变量修改对象内部数据
    • 另一个变量看到的是修改之后的数据
  2. 2 个引用变量指向同一个对象
    • 让其中一个引用对象指向另一个对象
    • 另一个引用变量依然指向前一个对象
// 例1
var obj1 = { name: "Tom" }
var obj2 = obj1
obj2.age = 12
console.log(obj1.age) // 12
function fn(obj) {
    obj.name = "A"
}
fn(obj1)
console.log(obj2.name) // A

// 例2
var a = { age: 12 }
var b = a
a = { name: "Bob", age: 13 }
b.age = 14
console.log(b.age, a.name, a.age) // 14 Bob 13
function fn2(obj) {
    obj = { age: 15 }
}
fn2(a)
console.log(a.age) //13

问题 7: 在 JS 调用函数时传递变量参数时, 是 值传递 还是 引用传递

  1. 都是值(基本/地址值)传递
  2. 可能是值传递 也可能是引用传递(地址值)
var a = 3
function fn(a) {
    a = a + 1
}
fn(a)
console.log(a) // 3
console.log(fn(a)) // 4

问题 8: JS 引擎如何管理内存?

  1. 内存生命周期

    • 分配小内存空间,得到使用权
    • 存储数据,可以反复进行操作
    • 释放小内存空间
  2. 释放内存

    • 局部变量: 函数执行完自动释放
    • 对象 : 成为垃圾对象 => 垃圾回收器回收

3. 对象

3.1 对象的分类
  1. 内建对象

    • 由 ES 标准定义的对象 在任何的 ES 的实现中都可以使用
    • 比如 Math String Number Function Object...
  2. 宿主对象

    • 由 JS 的运行环境提供的对象 目前来讲主要指浏览器提供的对象
    • 比如 BOM DOM
  3. 自定义对象

    • 由开发人员之间创建的对象
3.2 基本和引用数据类型
  1. 引用数据类型
    • Object
  2. 在 JS 中变量都是保存到栈内存中的
    • 基本数据类型的值基本在栈内存中存储
    • 值与值之间是独立存在的
  3. 对象是保存在堆内存中的
    • 每开辟一个新的对象时,在堆内存中开辟一个新的空间
    • 而变量保存的是对象的内存地(对象的引用),如果两个变量保存的是同一个变量的引用,当一个变量修改变量值,另一个也会变化
3. 对象的相关问题

问题 1:什么是对象?

  • 多个数据的封装体
  • 用来保存多个数据的容器
  • 一个对象代表现实中的一个事物

问题 2:为什么要用对象?

  • 统一管理多个数据

问题 3:对象的组成?

  • 属性: 属性名(字符串)和属性值(任意类型)组成
  • 方法: 一种特别的属性(属性值是函数)

问题 4:如何访问对象内部数据?

  • 属性名: 编码简单 有时不能用
  • ['属性名'](属性值) :编码复杂 常用

问题 5: 什么时候必须使用['属性名']的方式?

  • 属性名包含特殊字符: - 空格
  • 属性名不确定
var p = {}
// 特殊字符
p["content-type"] = "text/json"
console.log(p["content-type"])

// 属性名不确定
var propName = "myAge"
var value = 18
p[propName] = value
console.log(p[propName])

4. 函数

4.1 返回值的类型
  • 返回值可以是任意的数据类型
  • 也可以是一个对象
  • 也可以是一个函数
4.2 枚举对象
  1. for ... in 语句:对象中有几个属性,循环体就会执行几次
  2. 每次执行时,会将对象中的一个属性的名字赋值给变量
for (var n in obj) {
    // 输出obj对象中的所有属性名
    console.log("属性名:" + n)

    // 输出obj对象中的所有属性值
    console.log("属性值:" + obj[n])
}
4.3 全局作用域
  • 作用域指一个变量的范围

  • 在 JS 中一共有两种作用域

    • 直接编写在 script 标签中的 JS 代码都在全局作用域
    • 全局作用域在页面打开时创建,在页面关闭时销毁
    • 在全局作用域中有一个全局对象 window
    • 它代表的是一个浏览器的窗口
    • 它由浏览器创建,可以直接使用
  • 在全局作用域中

    • 创建的变量都会作为 window 对象的属性保存
    • 创建的函数都会作为 window 对象的方法保存
    • 全局作用域中的变量都是全局变量
  • 在页面的任意部分都可以访问到

    作用域.jpg
4.4 IIFE
  1. 理解 (立即调用函数表达式)
    • 全称: Immediately-Invoked Function Expression
  2. 作用
    • 隐藏实现
    • 不会污染外部(全局)命名空间
    • 用来编写 Js 模块

注意:立即执行函数,往往只会执行一次

4.5 声明提前
  1. 变量的声明提前
    • 使用 var 关键字的变量,会在所有的代码执行之前被声明
      • 但是如果声明变量时不适用 var 关键字,则变量不会被声明提前
  2. 函数的声明提前
    • 使用函数声明形式创建的函数 function 函数(){}
      • 它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
    • 使用函数表达式创建的函数,不会被声明提前,所以不能声明前调用
4.6 函数作用域
  • 调用函数时创建函数作用域,函数执行完毕以后,函数作用域被销毁
  • 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
  • 在函数作用域中可以访问到全局作用域的变量
    • 全局作用域中无法访问到函数作用域的变量
  • 当在函数作用域操作一个变量时,它会现在自身作用域中寻找 如果有直接调用
    • 如果没有则向上一级中寻找,直到找到为止如果都没有就会报错
  • 在函数中要访问全局变量可以使用 window 对象
4.7 函数的对象 call()/apply()
  • 都需要函数对象的方法,需要通过函数对象来调用
  • 在调用 call()和 apply()可以将一个对象指定为第一个参数
    • 此时这个对象将会成为函数执行的 this
  • call()方法可以将实参在对象之后依次传递
  • apply()方法需要将实参封装在一个数组中统一传递
function fun(a, b) {
    console.log("a =" + a)
    console.log("b =" + b)
    alert(this)
}
var obj = {
    name: "obj1",
    sayName: function () {
        alert(this.name)
    },
}
var obj2 = {
    name: "obj2",
}
fun.call() // 等同于 fun.apply() == fun()

// 指定参数
fun.call(obj) // this = obj
obj.sayName.apply(obj2) // obj2

fun.call(obj, 2, 3) // a = 2, b = 3
fun.call(obj, [2, 3]) // a = 2, b = 3
4.8 列表参数

在调用函数时,浏览器每次都会传递两个隐含参数

  1. 函数的上下对象 this

  2. 封装实参的对象 argument

    • argument 是一个类数组对象,也可以通过索引来操作数据 也可以获取长度
    • 在调用函数时,我们所传递的实参都会在 argument 中保存
    • 即使不定义形参,也可以通过 argument 来使用实参 (过于麻烦)
  3. 属性 callee

    • 这个属性对应一个函数对象,就是当前正在指向的函数的对象
4.9 构造函数
  • 构造函数就是一个普通的函数,创建函数和普通函数没有区别

  • 不同的是构造函数习惯上首字母大写

  • 构造函数和普通函数的区别就是调用方式的不同

    • 普通函数是直接调用
    • 构造函数则需要使用关键字 new 来调用
  • 使用同一个构造函数创建的对象,我们将这一类对象,称为构造函数的一个类

  • 所有的对象都是 Object 的后代

  • 构造函数的执行流程

    1. 立即创建一个新的对象
    2. 将新建的对象设置为函数中的 this,在新建的对象中可以使用 this 来引用新建的函数
    3. 逐行执行函数中的代码
    4. 将新建的对象作为返回值返回
function Person(name, age) {
    // alert(this);  // this 等同于 per
    this.name = name
    this.age = age
}
// 构造函数
var per = new Person("孙悟空", 18)
var per2 = new Person("猪八戒", 18)
4.10 函数中的 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 (this) {
        console.log(this)
        this.color = color
    }
}
Person("red") //this是谁? window

var p = new Person("yellow") //this是谁? p

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()
4.11 回调函数的相关问题

问题 1:什么函数才是回调函数?

  • 1). 你定义的
  • 2). 你没有调用
  • 3). 但最终执行了

问题 2:常见的回调函数?

  • dom 事件回调函数
  • 定时器回调函数
  • ajax 请求回调函数
  • 生命周期回调函数
4.9 函数的相关问题

问题 1:什么是函数?

  • 实现特定功能的 n 条语句的封装体
  • 只有函数时可以执行的 其他类型的数据不能执行

问题 2:为什么要用函数?

  • 提高代码复用
  • 便于阅读交流

问题 3:如何定义函数?

  • 函数声明
  • 表达式
// 函数声明
function fun1() {
    console.log("fun1()")
}

// 表达式
var fun2 = function () {
    console.log("fun2()")
}

问题 4:如何调用(执行)函数?

  • test(): 直接调用
  • obj.test(): 通过对象调用
  • new test(): new 调用
  • test.call/apply(obj): obj 调用

5. math

  1. 绝对值

    Math.abs()

  2. x 的 y 次方

    Math.pow(x, y)

  3. 上舍入

    Math.ceil(1.3) // 2

  4. 下舍入

    Math.floor(1.8) // 1

  5. 四舍五入

    Math.round(1.3) // 1

  6. random 函数=> 随机生成 0-1 之间的随机数

    Math.random()

  7. x-y 之间的随机数

    Math.round(Math.random() * (y - x) + x)

6. 数组

  1. push()

    在数组末尾添加一个或多个元素

  2. pop()

    删除数组末尾的一个元素,该值并作为返回值返回

  3. unshift()

    在数组的头部添加一个或多个元素,并返回长度

  4. shift()

    删除末尾的一个元素,该值并作为返回值返回

  5. forEach(value,index,obj)

    变量整个数组
    value => 当前遍历的元素
    index => 当前遍历的元素的索引
    obj => 当前遍历的数值

  6. splice(x, y, z)

    可以删除、添加、替换
    x => 开始的位置
    y => 删除的个数
    z => 添加的元素(一个或多个)

  7. concat()

    可以合并两个或多个数组,并返回新的数组

  8. join()

    可以将数组转换为字符串,可以指定一个字符串作为连接符,不指定默认为 ',' 作为连接符

  9. reverse()

    将数组按照倒序的方式排列(针对于纯数字),根据 unicode 编码

  10. sort()

    将数组按照正序的方式排列(针对于纯数字),根据 unicode 编码

7. BOM

  • 浏览器对象模型

  • BOM 可以使我们通过 JS 来操作浏览器

  • 在 BOM 中为我们提供一组对象,用来完成对浏览器的操作

  • BOM 对象

    1. Window

      • 代表的是整个浏览器的窗口
      • 同时 window 也是网页中的全局对象
    2. Navigator

      • 代表的当前浏览器的信息通过该对象可以来识别不同的浏览器
    3. Location

      • 代表当前浏览器的地址信息
      • 可以通过 Location 获取地址栏信息,或者跳转界面
    4. History

      • 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史
      • 由于隐私原因,该对象不能获取到具体的历史记录 只能操作浏览器向前或向后翻页
      • 而且该操作只在当次访问时有效
    5. Screen(相对较小)

      • 代表用户的屏幕的信息,通过对象可以获取用户的显示器的相关信息
  • 这些 BOM 对象在浏览器中都是 window 对象的属性保存

    • 可以通过 window 对象来使用,也可以直接使用

8. JSON

  • JS 中的对象只有 JS 自己认识 其他的语言都不认识

  • JSON 就是一个特殊格式的字符串,这个字符串可以被任意语言所识别

    • 并且可以转换为任意语言中的对象
    • JSON 在开发中主要用来数据的交互
  • JSON

    • JavaScript Object Notation JS 对象表示法
    • JSON 和 JS 对象的格式一样,只不过 JSON 字符串中的属性名必须加双引号
  1. JSON 分类:

    1. 对象 {}
    2. 数组 {}
  2. JSON 中允许的值:

    1. 字符串
    2. 数值
    3. 布尔值
    4. 空值(null)
    5. 对象
    6. 数组
//  将JSON字符串转换成JS中的对象
var json = '{"name": "孙悟空", "age": 18, "sex": "男"}'

// json --> js对象
// JSON.parse()
var o = JSON.parse()
console.log(o.name)

// js对象 ---> json
// JSON.stringify()
var obj = { name: "孙悟空", age: 18, sex: "男" }
var str = JSON.stringify()

第二章 函数高级

1. 闭包

1.1 认识闭包
  1. 含义(如何产生闭包?)
    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就会产生闭包
  2. 理解(闭包到底是什么?)
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中
  3. 条件(产生闭包的条件?)
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
    • 执行外部函数
function fn1() {
    var a = 2
    var b = "abc"
    function fn2() {
        // 执行函数定义就会产生闭包(不用调用内部函数)
        console.log(a)
    }
    fn2()
}
fn1()
1.2 常见的闭包
  1. 将函数作为另一个函数的返回值
function fn1() {
    var a = 2
    function fn2() {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1()
f() // 3
f() // 4
  1. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
    setTimeout(function () {
        // 闭包包含msg 不包括time
        alert(msg)
    }, time)
}
showDelay("Tom", 2000)
1.3 闭包的作用
  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题 1:函数执行完后, 函数内部声明的局部变量是否还存在?

  • 一般情况下不存在, 只有存在于闭包里的变量才会存在

问题 2:在函数外部能直接访问函数内部的局部变量吗?

  • 不能, 但我们可以通过闭包让外部操作它
1.4 闭包的生命周期
  1. 产生: 在嵌套内部函数定义执行完时就产生了(不会再调用)
  2. 死亡: 在嵌套的内部函数成为垃圾对象时
function fn1() {
    // 此时闭包就已经产生了{函数提升,内部函数对象已经创建了}
    var a = 2
    function fn2() {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1()
f() // 3
f() // 4

f = null // 闭包死亡(包含闭包的函数对象成为垃圾对象)
1.5 闭包的应用
  • 具有特定功能的 js 文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含 n 个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
1.6 闭包的缺点及解决
  1. 缺点
    • 函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长
    • 容易造成内存泄露
  2. 解决
    • 能不用闭包就不用
    • 及时释放

2. 原型

2.1 认识原型
  1. 函数的 prototype 属性

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

    • 作用:
      • 函数的所有实例对象自动拥有原型中的属性(方法)
function fun() {}
fun.prototype.test = function () {
    console.log("test()")
}
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()")
}
2.2 显示原型与隐式原型
  1. 每个函数 function 都有一个 prototype,即显式原型(属性)
  2. 每个实例对象都有一个__proto__,即隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显示原型的值
  4. 内存的结构
  5. 总结:
    • 函数的 prototype 属性: 在定义函数时自动添加的
      • 默认值是一个空 Object 对象
    • 对象的__proto__属性: 创建对象时自动添加
      • 默认值是构造函数的 prototype 属性值
    • 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6 之前)
// 定义构造函数
function Fn() {
    // 内部语句:this.prototype = {}
}

// 1. 每个函数function都有一个prototype, 即显式原型
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()
原型.jpg
2.3 原型链
  1. 原型链

    • 访问一个对象的属性时
      • 先在自身属性中查找,找到返回
      • 如果没有,再沿着__proto__这条链向上查找,找到返回
      • 如果最终没找到,返回 undefined
    • 别名:隐式原型链
    • 作用:查找对象的属性(方法)
    原型链.jpg
  2. 构造函数/原型/实体对象的关系

    构造函数,原型,实例对象的关系.jpg
  3. 函数的显式原型指向的对象默认是空 Object 实例对象(但 Object 不满足)

  4. 所有函数都是 Function 的实例(包含 Function)

  5. Object 的原型对象是原型链尽头

2.4 原型链的属性
  1. 读取对象的属性值时: 会自动到原型链中查找
  2. 设置对象的属性值时: 不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
  3. 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
2.5 探索 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(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true

function Foo() {}
console.log(Object instanceof Foo) // false
探索instanceof案例2.jpg

3. 执行上下文和执行上下文栈

3.1 声明提前
  1. 变量声明提升
    • 通过 var 定义(声明)的变量,在定义语句之前就可以访问到
    • 值: undefined
  2. 函数声明提升
    • 通过 function 声明的函数,在之前就可以直接调用
    • 值: 函数定义(对象)
// 面试题
var a = 3
function fn() {
    console.log(a)
    var a = 4
}
fn() // undefined
3.2 执行上下文
  1. 代码分类(位置)

    • 全局代码
    • 函数(局部)代码
  2. 全局执行上下文

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

    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的,存在于栈中)
    • 对局部数据进行预处理
      • 形参变量==>赋值(实参)==>添加为执行上下文的属性
      • arguments==>赋值(实参列表), 添加为执行上下文的属性
      • var 定义的局部变量==>undefined, 添加为执行上下文的属性
      • function 声明的函数==>赋值(fun), 添加为执行上下文的方法
      • this==>赋值(调用函数的对象)
    • 开始执行函数体代码
// 函数执行上下文
function fn(a1) {
    console.log(a1) // 2
    console.log(a2) // undefined
    a3() // a3()
    console.log(this) // window
    console.log(arguments) // 伪数组(2, 3)

    var a2 = 3
    function a3() {
        console.log("a3()")
    }
}
fn(2, 3)
3.3 执行上下文栈
  1. 在全局执行代码前, JS 引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后, 将栈定的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下 window
// 1.进入全局执行上下文
var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b) // 3.进入foo执行上下文
}
var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}
bar(10) // 进入bar函数执行上下文
3.4 相关面试题
  • 面试题 1: 依次输出什么?

    • global begin: undefined
    • foo() begin: 1
    • foo() begin: 2
    • foo() begin: 3
    • foo() end: 3
    • foo() end: 2
    • foo() end: 1
    • global end: 1
  • 面试题 2: 整个过程中产生几个执行上下文?

    • 5
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)

4. 作用域

4.1 认识作用域
  1. 理解
    • 就是一块"地盘", 一个代码段所在的区域
    • 它就是静态的(相对于上下文对象), 在编写代码时就确定了
  2. 分类
    • 全局作用域
    • 函数作用域
    • 没有快作用域(ES6 有了==> let 创建的 )
  3. 作用
    • 隔离变量, 不同作用域下同名变量不会有冲突
4.2 作用域与执行上下文
  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) // fn() 100 20 300 10
    function bar(x) {
        var a = 1000,
            d = 400
        console.log("bar()", a, b, c, d, x) // bar() 1000 20 300 400 10
    }

    bar(100)
    bar(200)
}
fn(10)
作用域.jpg

5. 内存溢出与内存泄露

  1. 内存溢出
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过剩下的内存时, 就会抛出内存溢出的错误
  2. 内存泄露
    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:
      • 意外的全局变量
      • 没有及时清理计时器或回调函数
      • 闭包

第三章 对象高级

1. 对象创建模式

1.1 Object 构造函数模式
  • 套路: 先创建空 Object 对象, 再动态添加属性/方法
  • 使用场景: 起始时不确定对象的内部数据
  • 问题: 语句太多
var p = new Object()
p.name = "Tom"
p.age = 12
p.setName = name => (this.name = name)

// 测试
p.setName("Jack")
console.log(p.name, p.age)
1.2 对象字面量模式
  • 套路: 使用{}创建对象, 同时指定属性/方法
  • 使用场景: 起始时对象内部数据是确定的
  • 问题: 如果创建多个对象, 有重复代码
var p = {
    name: "Tom",
    age: 12,
    setName(name) {
        this.name = name
    },
}
// 测试
console.log(p.name, p.age)
1.3 工厂模式
  • 套路: 通过工厂函数动态创建对象并返回
  • 使用场景: 需要多个对象
  • 问题: 对象没有一个具体的类型, 都是 Object 类型
function createPerson(name, age) {
    // 返回一个对象的函数===>工厂函数
    var obj = {
        name: name,
        age: age,
        setName(name) {
            this.name = name
        },
    }
    return obj
}

// 测试
var p1 = createPerson("Tom", 12)
var p2 = createPerson("Bob", 12)
1.4 自定义构造函数模式
  • 套路: 自定义构造函数,通过 new 创建对象
  • 使用场景: 需要创建多个类型确定的对象
  • 问题: 每个对象都有相同的数据, 浪费内存
function Person(name, age) {
    this.name = name
    this.age = age
    this.setName = name => (this.name = name)
}

var p1 = new Person("Tom", 12)
1.5 构造函数+原型的组合模式
  • 套路: 自定义构造函数,属性在函数中初始化,方法添加到原型上
  • 使用场景: 需要创建多个类型确定的对象
function Person(name, age) {
    // 在构造函数只初始化一般函数
    this.name = name
    this.age = age
}
Person.prototype.setName = name => (this.name = name)

2. 继承模式

2.1 原型链继承
  1. 套路
    • 定义父类型构造函数
    • 给父类型的原型添加构造函数
    • 定义子类型的构造函数
    • 创建父类型的对象赋值给子类型的原型
    • 将子类型原型的构造属性设置为子类型
    • 给子类型原型添加对象
    • 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    • 子类型的原型为父类型的一个实例对象
// 父类型
function Supper() {
    this.supProp = "Supper property"
}
Supper.prototype.showSupperProp = () => console.log(this.supProp)

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

var sub = new Sub()
sub.showSubProp()
sub.showSupperProp()
console.log(sub.constructor) // Sub
2.2 组合继承(原型链+构造函数)
  1. 利用原型链实现对父类型对象的方法继承
  2. 利用 supper() 借用父类型构建函数初始化相同属性
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.setName = name => (this.name = name)

function Student(name, age, price) {
    // 为了得到属性
    Person.call(this, name, age)
    this.price = price
}
//为了能看到父类型的方法
Student.prototype = new Person()
// 修正constructor属性
Student.prototype.constructor = Student
Student.prototype.setPrice = price => (this.price = price)

var s = new Student("Tom", 12, 10000)
s.setName("Bob")
s.setPrice(15000)
console.log(s.name, s.age, s.price)

第四章 线程机制和事件

1. 浏览器的时间循环(轮询)

  1. 多有代码分类
    • 初始化执行代码(同步代码): 包含 dom 事件监听, 设置定时器, 发送 ajax 请求的代码
    • 回调执行代码(异步代码): 处理回调逻辑
  2. js 引擎执行代码的基本流程
    • 初始化代码===>回调代码
  3. 模型的 2 个重要组成部分
    • 事件管理模块
    • 回调队伍
  4. 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调队列中
    • 只有当初始化代码执行完后(可以要一定时间), 才会遍历读取回调队列中的回调函数执行


      浏览器的事件循环(轮询)模型.jpg

2. Web Workers 测试

  1. H5 规范了 js 分线程的实现, 取名为: Web Workers
  2. 相关 API
    • Worker: 构造函数, 加载分线程执行的 js 文件
    • Worker.prototype.onmessage: 用于接收一个线程的回调函数
    • Worker.prototype.postMessage: 向另一个线程发送消息
  3. 不足
    • worker 内代码不能操作 DOM(更新 UI)
    • 不能跨越加载 JS
    • 不是每个浏览器都支持这个新特性
<!-- html -->
<input type="text" placeholder="数值" id="number" />
<button id="btn">计算</button>
<script>
    var btn = document.getElementById("btn")
    var input = document.getElementById("number")
    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)
    }
</script>

<!-- worker.js -->
<script>
    /** 递归调用
     * f(n)=f(n-1)+f(n-2)
     * 1 1 2 3 5 8
     * @param {*} n
     * @return {*}
     */
    function fibonacci(n) {
        return n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
    }

    var onmessage = function (event) {
        var number = event.data
        console.log("分线程接收主线程返回的数据:" + number)
        // 计算
        var result = fibonacci(number)
        postMessage(result)
        console.log("分线程向主线程返回数据:" + result)
    }
</script>
线程图解.jpg
  • 多线程


    多线程(图解).jpg
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容