1. 数据
1.1 数据类型
基本数据类型: Undefined
、Null
、Boolean
、Number
、String
、Symbol
引用数据类型: Object
1.2 数据存储
基本数据类型储存在栈内存中,变量中的值即是实际数据的值,变量的赋值即为值的复制。
const num1 = 5 // 在栈内存中生成一个值为5的数据赋值给num1
const num2 = num1 // 在栈内存中再次生成一个值为5的数据赋值给num2
// num1 和 num2 实际是两片不同的栈内存数据,只是值相同,互相独立
引用数据类型存储在堆内存中,变量中的值是实际数据的地址,变量的复制为地址的复制。
const obj1 = {} // 在堆内存中生成一个空对象,将对象的引用地址赋值给obj1
const obj2 = obj1 // 不对堆内存做任何操作,将对象obj1的引用地址复制给obj2
// obj1 和 obj2 实际是同一片堆内存数据,除变量名不同外,两者完全相同,修改对象会互相影响
1.3 数据检测与比较
typeof
检测变量数据类型。注:typeof null = 'object'
相等('==')
: 数据类型不一致时自动进行转换后比较。注:引用数据类型调用 valueOf
方法
const obj1 = {}
const obj2 = {
valueOf () { return 1 }
}
console.log(obj1 == 1, obj2 == 1) // false true
全等('===')
: 比较数据类型与值,都为引用数据类型时比较地址
2. 变量
2.1 定义变量
JS 的变量是松散类型,可以用于保存任何类型的数据,默认值为 undefined
var
: 变量提前声明到作用域顶部,在定义前使用值为默认值,可重复声明相同变量,可修改值
const
: 无提前声明,不可在定义前使用,不可重复定义,不可修改值
let
: 无提前声明,不可在定义前使用,不可重复定义,不可修改值
2.2 提前声明
变量提前声明: 使用var定义变量时,变量会在函数最顶层声明,在运行行赋值
console.log(a, b) // undefined undefined
if (false) {
var a = 10
} else {
var b = 10
(function f() {
console.log(c) // undefined
var c = 10
console.log(c) // 10
}())
}
console.log(a, b) // undefined 10
console.log(c) // 报错
函数提前声明: 声明函数时,函数会被提前到作用域顶部并定义。注:函数表达式不会提前声明。
f1() // 1
function f1() {
console.log(1)
}
f2() // 报错
if (1) {
// f2只会被提前到这
function f2() {
console.log(2)
}
}
f2() // 2
函数优先: 函数声明提前声明优先级大于变量。(个人更倾向于是由于函数声明自带赋值,而变量提升只是声明)
// function f1 和 var f1都被提升到这
// 如 function f1在前,那么var f1后运行时f1这个变量已存在,var f1失效
// 如 var f1在前,那么function f1后运行,f1这个变量已存在,不再定义新变量,将function赋值给f1
// 无聊哪种情况f1的值最终都会是function f1的值,因此个人觉得不存在优先级问题
f1() // 1
var f1
f1() // 1
var f1 = function () {
console.log(2)
}
f1() // 2
function f1() {
console.log(1)
}
2.3 块作用域
使用 const
、let
定义的变量,只可在当前块中使用
{
// console.log(a) // 报错
const a = 1
console.log(a) // 1
// const a = 2 // 报错
}
console.log(a) // 报错,a is not defined
2.4 全局作用域和函数用域
全局作用域: 最外层作用域
函数作用域: 生成一个类似于块作用域的执行环境
3. 环境
3.1 作用域链
用于保证对执行环境有权访问的所有变量和函数的有序访问
访问规则:
- 同层作用域有相同的访问权限
- 外部作用域无法访问内部作用域
- 内部作用域可以访问外部作用域
- 同层的不同内部作用域无法相互访问
- 变量访问遵循就近原则(获取距离当前作用域最近的变量值)
3.2 环境上下文
大致可理解为作用域链,即当前位置可访问的变量
可具象为一个对象,对象包括如下内容:
- 当前可访问的变量: 变量的值
- 当前可访问的函数: 函数的值
- this: this的值
与作用域链的区别是:
- 作用域及作用域链在定义时已经确定了可访问的变量
- 环境上下文不仅需要可访问变量,还需要变量的值。由于值在运行时是变化的,因此只有在执行到当前上下文时才能确定具体的值
3.3 this
调用当前函数的对象
绑定规则:
- 默认绑定:直接调用不带任何修饰,this指向全局对象
function f () { return this }
console.log(f()) // window, 没有全局对象则是undefined
- 隐式绑定:由对象链式调用,this 指向调用对象
function f () { return this }
const obj = {
f: f
}
console.log(obj.f() === obj) // true
- 硬绑定: 使用.call、.apply、.bind
function f () { return this }
const obj = {}
console.log(f.call(obj) === obj) // true
- new绑定: 使用new 构造函数
function f (a) { this.a = a }
const obj = new f('a')
console.log(obj.a) // 'a'
- 箭头函数:
const that = this
const obj1 = {
a: 'a',
f: () => {
console.log(this === that)
}
}
const obj2 = { a: 'b' }
obj1.f.call(obj2) // true
4. 闭包
4.1 什么是闭包
闭包:指有权访问另一个函数作用域中的变量的函数
var result
function heal(HP, healer) {
return function(currentHP, target) {
console.log(
`${target} get ${HP} point heal from ${healer}, total HP is ${currentHP + HP}`
)
}
}
heal(100, 'Soraka')(500, 'Teemo') // Teemo get 10 point heal from Soraka, total HP is 600
4.2 函数生成图示
函数的生成产物:
- 生成执行环境和作用域链
- 生成函数的活动对象
heal函数生成图示:
闭包函数生成图示:
4.3 垃圾回收机制
4.3.1 标记清除
个人更愿意称之为可达性算法,逻辑如下:
- 从跟节点开始往下遍历,将所有可访问的变量以及变量所引用的变量进行标记
- 再次遍历,清除所有未被标记的变量
4.3.2 引用计数
存在缺陷,因此比较少用
- 记录每个值被变量所引用的次数。赋值给变量,引用次数加一;变量取消引用(释放变量或引用其他值),引用次数减一
- 释放所有引用次数为零的变量
缺陷:
循环引用无法释放,如对象A引用了B对象,B对象也引用了A对象,但是A和B都没有变量可以访问
4.4 闭包产生
无闭包情况:
- 函数生成产生的执行环境和作用域链在函数
- 执行完毕后释放执行环境和作用域链
- 垃圾回收机制工作,由于函数变量对象不再有变量引用,也无法获取,销毁函数变量对象
有闭包产生情况:
- 生成heal函数的执行环境以及作用域链。作用域链指向:heal的变量对象、全局变量对象
- 生成匿名函数的执行环境以及作用域链。作用域链指向:匿名函数的变量对象、heal的变量对象、全局变量对象
- heal函数执行完毕返回匿名函数的引用地址,销毁heal函数的执行环境以及作用域链
- 垃圾回收机制工作,由于heal的函数变量对象被匿名函数的作用域链做访问,不会被销毁
- 执行匿名函数,产生闭包
4.5 闭包的作用
- 用于访问函数内部的变量
- 阻止函数调用后数据被清
- 利于封装,避免造成污染
4.6 使用场景
- 回调传参
async function check (callback) {
const isCheck = await api.check() // 后台获取状态
return callback(isCheck)
}
check((isCheck) => {
if (isCheck) {
console.log('checked')
} else {
console.log('Not check')
}
})
- 构造函数
function Hero(name, age) {
this.name = name
this.age = age
this.introduction = function () {
console.log(`my name is ${name}, my age is ${age}`)
}
}
const Soraka = new Hero('soraka', 18)
Soraka.introduction()
- setTimeout传参
function check (res) {
return function () {
console.log(res)
}
}
setTimeout(check(1), 1000) // 原生无法传参,传入会报错
- 循环绑定事件
const divList = document.getElementsByClassName('div')
Array.from(divList).forEach((item) => {
item.addEventListener('click', () => {
console.log(item.innerHTML)
})
})
- 模块机制
个人无法肯定JS模块的实现是完全的闭包,但是效果类似。
模块的实现机制是在外层调用函数运行模块文件中的代码并返回一个对象
function module() { // JS运行时添加
// 模块中的代码
const num= 1 // 模块中的变量
return { // 类似 export default {}
getNumber () {
console.log(num)
}
}
}
const numObj = module() // 类似 import numObj from 'module'
numObj.getNumber() // 调用模块中的函数并范围变量