目录
- 第一章、基础总结
-
- 数据类型(字面量)
- 分类
- 判断
- 相关问题
-
- 数据·变量·内存
- 类型转换
- 运算符
- Unicode 编码
- 条件运算符(三元运算符)
- 流程控制语句
- 相关问题
-
- 对象
- 对象的分类
- 基本和引用数据类型
- 对象的相关问题
-
- 函数
- 返回值的类型
- 枚举对象
- 全局作用域
- IIFE
- 声明提前
- 函数作用域
- 函数的对象 call()/apply()
- 构造函数
- 构造中的 this
- 回调函数的相关问题
- 函数的相关问题
- Math
- 数组
- BOM
- JSON
-
- 第二章、函数高级
-
- 闭包
- 认识闭包
- 常见的闭包
- 闭包的作用
- 闭包的生命周期
- 闭包的缺点及解决
-
- 原型
- 认识原型
- 显示原型与隐式原型
- 原型链
- 原型链的属性
- 探索 instanceof
-
- 执行上下文和执行上下文栈
- (变量/函数)声明提前
- 执行上下文
- 执行上下文栈
- 相关面试题
-
- 作用域
- 认识作用域
- 作用域与执行上下文
- 内存溢出与内存泄露
-
- 第三章、对象高级
-
- 对象创建模式
- Object 构造函数模式
- 对象字面量模式
- 工厂模式
- 自定义构造函数模式
- 构造函数+原型的组合模式
-
- 继承模式
- 原型链继承
- 组合继承(原型链+构造函数)
-
- 第四章、线程机制和事件
- 浏览器的时间循环(轮询)
- 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 的区别?
- undefined 代表定义未赋值、
- null 代表了定义了并已经赋值
问题 2:什么时候给变量赋值为 null?
- 初始赋值 => 表明将要赋值
- 结束赋值 => 让对象被回收
问题 3:严格要求变量的类型与数据类型?
- 数据的类型
- 数据类型
- 对象类型
- 引用的类型(变量内存值的类型)
- 基本类型:保存就是基本类型的数据
- 引用类型:保存的是地址值
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:什么是数据?
- 存储在内存中代表特定信息的(本质上就是 0100)
- 数据的特点:
- 可传递
- 可运算
- 一切皆数据
- 内存中所有操作的目标:数据
- 算术运算
- 逻辑运算
- 赋值
- 运行函数
问题 2:什么是内存?
- 内存条通电后产生的可存储数据的空间(临时的)
- 内存产生和死亡:
- 内存条(电路板) ==> 通电 ==> 产生内存空间 ==> 存储数据 ==> 处理数据 ==> 断电 ==> 内存空间的数据消失
- 一块小内存的 2 个数据
- 内存存储的数据
- 地址值
- 内存分类
- 栈: 全局变量/局部变量
- 堆: 对象
问题 3:什么是变量?
- 可变化的量,由变量名和变量值组成
- 每个变量都有对应一个小内存
- 变量名用来查找对应的内存
- 变量值就是内存中保存的数据
问题 4:内存,数据,变量 三者之间的关系?
- 内存用来存储数据的空间
- 变量是内存的标识
问题 5: var a = xxx, a 内存中到底保存的是什么?
- xxx 是基本数据 --> 数据
- xxx 是一个对象 --> 对象的地址值
- xxx 是一个变量 --> xxx 的内容
问题 6: 关于引用变量赋值问题
- 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 调用函数时传递变量参数时, 是 值传递 还是 引用传递
- 都是值(基本/地址值)传递
- 可能是值传递 也可能是引用传递(地址值)
var a = 3
function fn(a) {
a = a + 1
}
fn(a)
console.log(a) // 3
console.log(fn(a)) // 4
问题 8: JS 引擎如何管理内存?
-
内存生命周期
- 分配小内存空间,得到使用权
- 存储数据,可以反复进行操作
- 释放小内存空间
-
释放内存
- 局部变量: 函数执行完自动释放
- 对象 : 成为垃圾对象 => 垃圾回收器回收
3. 对象
3.1 对象的分类
-
内建对象
- 由 ES 标准定义的对象 在任何的 ES 的实现中都可以使用
- 比如 Math String Number Function Object...
-
宿主对象
- 由 JS 的运行环境提供的对象 目前来讲主要指浏览器提供的对象
- 比如 BOM DOM
-
自定义对象
- 由开发人员之间创建的对象
3.2 基本和引用数据类型
- 引用数据类型
- Object
- 在 JS 中变量都是保存到栈内存中的
- 基本数据类型的值基本在栈内存中存储
- 值与值之间是独立存在的
- 对象是保存在堆内存中的
- 每开辟一个新的对象时,在堆内存中开辟一个新的空间
- 而变量保存的是对象的内存地(对象的引用),如果两个变量保存的是同一个变量的引用,当一个变量修改变量值,另一个也会变化
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 枚举对象
- for ... in 语句:对象中有几个属性,循环体就会执行几次
- 每次执行时,会将对象中的一个属性的名字赋值给变量
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
- 理解 (立即调用函数表达式)
- 全称: Immediately-Invoked Function Expression
- 作用
- 隐藏实现
- 不会污染外部(全局)命名空间
- 用来编写 Js 模块
注意:立即执行函数,往往只会执行一次
4.5 声明提前
- 变量的声明提前
- 使用 var 关键字的变量,会在所有的代码执行之前被声明
- 但是如果声明变量时不适用 var 关键字,则变量不会被声明提前
- 使用 var 关键字的变量,会在所有的代码执行之前被声明
- 函数的声明提前
- 使用函数声明形式创建的函数 function 函数(){}
- 它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
- 使用函数表达式创建的函数,不会被声明提前,所以不能声明前调用
- 使用函数声明形式创建的函数 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 列表参数
在调用函数时,浏览器每次都会传递两个隐含参数
函数的上下对象 this
-
封装实参的对象 argument
- argument 是一个类数组对象,也可以通过索引来操作数据 也可以获取长度
- 在调用函数时,我们所传递的实参都会在 argument 中保存
- 即使不定义形参,也可以通过 argument 来使用实参 (过于麻烦)
-
属性 callee
- 这个属性对应一个函数对象,就是当前正在指向的函数的对象
4.9 构造函数
构造函数就是一个普通的函数,创建函数和普通函数没有区别
不同的是构造函数习惯上首字母大写
-
构造函数和普通函数的区别就是调用方式的不同
- 普通函数是直接调用
- 构造函数则需要使用关键字 new 来调用
使用同一个构造函数创建的对象,我们将这一类对象,称为构造函数的一个类
所有的对象都是 Object 的后代
-
构造函数的执行流程
- 立即创建一个新的对象
- 将新建的对象设置为函数中的 this,在新建的对象中可以使用 this 来引用新建的函数
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
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
- 绝对值
Math.abs()
- x 的 y 次方
Math.pow(x, y)
- 上舍入
Math.ceil(1.3) // 2
- 下舍入
Math.floor(1.8) // 1
- 四舍五入
Math.round(1.3) // 1
- random 函数=> 随机生成 0-1 之间的随机数
Math.random()
- x-y 之间的随机数
Math.round(Math.random() * (y - x) + x)
6. 数组
- push()
在数组末尾添加一个或多个元素
- pop()
删除数组末尾的一个元素,该值并作为返回值返回
- unshift()
在数组的头部添加一个或多个元素,并返回长度
- shift()
删除末尾的一个元素,该值并作为返回值返回
- forEach(value,index,obj)
变量整个数组
value => 当前遍历的元素
index => 当前遍历的元素的索引
obj => 当前遍历的数值 - splice(x, y, z)
可以删除、添加、替换
x => 开始的位置
y => 删除的个数
z => 添加的元素(一个或多个) - concat()
可以合并两个或多个数组,并返回新的数组
- join()
可以将数组转换为字符串,可以指定一个字符串作为连接符,不指定默认为 ',' 作为连接符
- reverse()
将数组按照倒序的方式排列(针对于纯数字),根据 unicode 编码
- sort()
将数组按照正序的方式排列(针对于纯数字),根据 unicode 编码
7. BOM
浏览器对象模型
BOM 可以使我们通过 JS 来操作浏览器
在 BOM 中为我们提供一组对象,用来完成对浏览器的操作
-
BOM 对象
-
Window
- 代表的是整个浏览器的窗口
- 同时 window 也是网页中的全局对象
-
Navigator
- 代表的当前浏览器的信息通过该对象可以来识别不同的浏览器
-
Location
- 代表当前浏览器的地址信息
- 可以通过 Location 获取地址栏信息,或者跳转界面
-
History
- 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史
- 由于隐私原因,该对象不能获取到具体的历史记录 只能操作浏览器向前或向后翻页
- 而且该操作只在当次访问时有效
-
Screen(相对较小)
- 代表用户的屏幕的信息,通过对象可以获取用户的显示器的相关信息
-
-
这些 BOM 对象在浏览器中都是 window 对象的属性保存
- 可以通过 window 对象来使用,也可以直接使用
8. JSON
JS 中的对象只有 JS 自己认识 其他的语言都不认识
-
JSON 就是一个特殊格式的字符串,这个字符串可以被任意语言所识别
- 并且可以转换为任意语言中的对象
- JSON 在开发中主要用来数据的交互
-
JSON
- JavaScript Object Notation JS 对象表示法
- JSON 和 JS 对象的格式一样,只不过 JSON 字符串中的属性名必须加双引号
-
JSON 分类:
- 对象 {}
- 数组 {}
-
JSON 中允许的值:
- 字符串
- 数值
- 布尔值
- 空值(null)
- 对象
- 数组
// 将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 认识闭包
- 含义(如何产生闭包?)
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就会产生闭包
- 理解(闭包到底是什么?)
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
- 条件(产生闭包的条件?)
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 执行外部函数
function fn1() {
var a = 2
var b = "abc"
function fn2() {
// 执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
fn2()
}
fn1()
1.2 常见的闭包
- 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
- 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
// 闭包包含msg 不包括time
alert(msg)
}, time)
}
showDelay("Tom", 2000)
1.3 闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题 1:函数执行完后, 函数内部声明的局部变量是否还存在?
- 一般情况下不存在, 只有存在于闭包里的变量才会存在
问题 2:在函数外部能直接访问函数内部的局部变量吗?
- 不能, 但我们可以通过闭包让外部操作它
1.4 闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不会再调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
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 闭包的缺点及解决
- 缺点
- 函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放
2. 原型
2.1 认识原型
-
函数的 prototype 属性
- 每个函数都有一个 prototype 属性
- 默认指向一个 Object 空对象(即称为:原型对象)
- 原型对象都有一个 constructor 属性
- 默认指向函数对象
- 每个函数都有一个 prototype 属性
-
给原型对象添加属性(一般都是方法)
- 作用:
- 函数的所有实例对象自动拥有原型中的属性(方法)
- 作用:
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 显示原型与隐式原型
- 每个函数 function 都有一个 prototype,即显式原型(属性)
- 每个实例对象都有一个
__proto__
,即隐式原型(属性) - 对象的隐式原型的值为其对应构造函数的显示原型的值
- 内存的结构
- 总结:
- 函数的 prototype 属性: 在定义函数时自动添加的
- 默认值是一个空 Object 对象
- 对象的
__proto__
属性: 创建对象时自动添加- 默认值是构造函数的 prototype 属性值
- 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6 之前)
- 函数的 prototype 属性: 在定义函数时自动添加的
// 定义构造函数
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()
2.3 原型链
-
原型链
- 访问一个对象的属性时
- 先在自身属性中查找,找到返回
- 如果没有,再沿着
__proto__
这条链向上查找,找到返回 - 如果最终没找到,返回 undefined
- 别名:隐式原型链
- 作用:查找对象的属性(方法)
原型链.jpg - 访问一个对象的属性时
-
构造函数/原型/实体对象的关系
构造函数,原型,实例对象的关系.jpg 函数的显式原型指向的对象默认是空 Object 实例对象(但 Object 不满足)
所有函数都是 Function 的实例(包含 Function)
Object 的原型对象是原型链尽头
2.4 原型链的属性
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
- 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
2.5 探索 instanceof
- instanceof 是如何判断的?
- 表达式: A instanceof B
- 如果 B 函数的显式原型对象是 A 对象的原型链上,返回 true,否则返回 false
- 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
3. 执行上下文和执行上下文栈
3.1 声明提前
- 变量声明提升
- 通过 var 定义(声明)的变量,在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过 function 声明的函数,在之前就可以直接调用
- 值: 函数定义(对象)
// 面试题
var a = 3
function fn() {
console.log(a)
var a = 4
}
fn() // undefined
3.2 执行上下文
-
代码分类(位置)
- 全局代码
- 函数(局部)代码
-
全局执行上下文
- 在执行全局代码前将 window 确定为全局执行上下文
- 对全局数据进行预处理
- var 定义的全局变量==>undefined, 添加为 window 的属性
- function 声明的全局函数==>赋值(fun), 添加为 window 的方法
- this==>赋值(window)
- 开始执行全局代码
-
函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的,存在于栈中)
- 对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- 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 执行上下文栈
- 在全局执行代码前, JS 引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后, 将栈定的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下 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 认识作用域
- 理解
- 就是一块"地盘", 一个代码段所在的区域
- 它就是静态的(相对于上下文对象), 在编写代码时就确定了
- 分类
- 全局作用域
- 函数作用域
- 没有快作用域(ES6 有了==> let 创建的 )
- 作用
- 隔离变量, 不同作用域下同名变量不会有冲突
4.2 作用域与执行上下文
- 区别 1
- 全局作用域之外, 每个函数都会创建自己的作用域, 作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后,js 代码马上执行之前创建
- 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
- 区别 2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
- 联系
- 执行上下文环境(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
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)
5. 内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过剩下的内存时, 就会抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理计时器或回调函数
- 闭包
第三章 对象高级
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 原型链继承
- 套路
- 定义父类型构造函数
- 给父类型的原型添加构造函数
- 定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型
- 给子类型原型添加对象
- 创建子类型的对象: 可以调用父类型的方法
- 关键
- 子类型的原型为父类型的一个实例对象
// 父类型
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 组合继承(原型链+构造函数)
- 利用原型链实现对父类型对象的方法继承
- 利用 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. 浏览器的时间循环(轮询)
- 多有代码分类
- 初始化执行代码(同步代码): 包含 dom 事件监听, 设置定时器, 发送 ajax 请求的代码
- 回调执行代码(异步代码): 处理回调逻辑
- js 引擎执行代码的基本流程
- 初始化代码===>回调代码
- 模型的 2 个重要组成部分
- 事件管理模块
- 回调队伍
- 模型的运转流程
- 执行初始化代码, 将事件回调函数交给对应模块管理
- 当事件发生时, 管理模块会将回调函数及其数据添加到回调队列中
-
只有当初始化代码执行完后(可以要一定时间), 才会遍历读取回调队列中的回调函数执行
浏览器的事件循环(轮询)模型.jpg
2. Web Workers 测试
- H5 规范了 js 分线程的实现, 取名为: Web Workers
- 相关 API
- Worker: 构造函数, 加载分线程执行的 js 文件
- Worker.prototype.onmessage: 用于接收一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息
- 不足
- 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