一、变量类型和计算
1. 值类型和引用类型
1.1 值类型
值类型变量存储在栈中(如:Undefined、String、Number、Boolean、Symbol)
1.2 引用类型
引用类型变量的内存地址存储在栈中,值存储在堆中(如:Object、Null、Array、Function)
1.3 浅拷贝
仅能拷贝指向值类型的对象属性,可使用 Object.assign({},obj) 或剩余运算符 {...obj}
1.4 深拷贝
彻底拷贝一个对象,对原对象无影响
/*** 深拷贝* @param { Object } obj - 要拷贝的对象* @returns { Object } result - 拷贝后的副本*/function deepClone(obj = {}) {if (typeof obj !== 'object' || obj == null) return objlet resultif (obj instanceof Array) {result = []} else {result = {}}for(let key in obj) {if (obj.hasOwnProperty(key)) {result[key] = deepClone(obj[key])}}return result}
2. 类型判断
2.1 typeof 运算符
- 能识别所有值类型
- 能识别函数
- 判断是否是引用类型(不可再细分)
3. 逻辑运算
逻辑运算中,javascript 会自动进行强制类型转换
3.1 字符串拼接
- 数字 + 字符串 = 字符串
- 布尔值 + 字符串 = 字符串
可使用 parseInt(string, radix) 和 Number(obj) 将字符串或对象转换为数字
const num1 = parseInt('10.2') // parseInt 转化的是整数const num2 = Number('10.2') // 另一种语法糖写法:+'10.2'console.log(num1) // 10console.log(num2) // 10.2
3.2 == 运算符
双等号会进行强制类型转换,除了排除 null 和 undefined 外,建议都用 ===
// 举例,以下均为 true100 == ‘100’0 == ‘’0 == falsefalse == ''null == undefined
3.3 if 语句和逻辑运算
- truly 变量:!!a === true 的变量
- falsely 变量:!!a === false 的变量
if 语句和逻辑运算符 &&、||、! 等均是通过 truly 和 falsely 变量判断的
二、原型和原型链
1、 class 和继承
1.1 class
- 类(class)包括 constructor、属性、方法
- class 是一个语法糖,实际上是一个 function
``javascript class Student { constructor(name, number) { this.name = name this.number = number } sayHi() { console.log(姓名:${this.name},学号:${this.number}`) } }
// 通过类声明对象/示例 const xialuo = new Student(‘夏洛’, 100) console.log(xialuo.name) console.log(xialuo.number) xialuo.sayHi()
<a name="NZYqg"></a>#### 1.2 继承继承主要有以下关键点- extends(关键字)- super(执行父类的构建过程)- 扩展或重写方法```javascript// 父类class People {constructor(name) {this.name = name}eat() {console.log(`${this.name} eat something`)}}// 子类class Student extends People {constructor(name, number) {super(name)this.number = number}sayHi() {console.log(`姓名:${this.name},学号:${this.number}`)}}// 子类class Teacher extends People {constructor(name, major) {super(name)this.major = major}teach() {console.log(`姓名:${this.name} 教授 ${this.major}`)}}const xialuo = new Student('夏洛', 100)console.log(xialuo.name)console.log(xialuo.number)xialuo.sayHi()xialuo.eat()const wanglaoshi = new Teacher('王老师', '语文')console.log(wanglaoshi.name)console.log(wanglaoshi.major)xialuo.sayHi()xialuo.eat()
2、类型判断 instanceof
判断是否通过该方法构建(new)出来的
// 该例子见第一小节console.log(xialuo instanceof Student) // trueconsole.log(xialuo instanceof People) // trueconsole.log(xialuo instanceof Object) // trueconsole.log([] instanceof Array) // trueconsole.log([] instanceof Object) // trueconsole.log({} instanceof Object) // true
3、原型和原型链
3.1 原型
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。
原型关系:
- 每个 class 都有显式原型 prototype
- 每个实例都有隐式原型 proto
- 实例的 proto 指向对应 class 的prototype
基于原型的执行规则
- 获取属性或执行方法时,先在自身属性和方法找
- 如果找不到则自动去 proto 中去找
// 该例子见第一小节// 隐式原型和显式原型console.log(xialuo.__proto__) // 隐式console.log(Student.prototype) // 显式console.log(xialuo.__proto__ === Student.prototype) // true
3.2 原型链
每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层最终指向 null,这就是原型链// 该例子见第一小节console.log(Student.prototype.__proto__) // 隐式console.log(People.prototype) // 显式console.log(Student.prototype.__proto__ === People.prototype) // true
三、作用域和闭包
1、作用域和自由变量
1.1 作用域
作用域代表了一个变量合法的使用范围,包括:
- 全局作用域
- 函数作用域
- 块级作用域
1.2 自由变量
自由变量即一个变量在当前作用域没有定义,但被使用了。它需要遵循下面的规则:
- 向上级作用域,一层一层依次寻找,直至找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
2、闭包
闭包是作用域应用的特殊情况,有两种表现:
- 函数作为参数被传递
- 函数作为返回值被返回
PS:
- 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!
- 下面两个例子返回值均为 100
2.1 常用场景
隐藏数据,实现简单的缓存工具
// 闭包隐藏数据,只提供 APIfunction createCache() {const data = {} // 闭包中的数据,被隐藏,不被外界访问return {set: function (key, val) {data[key] = val},get: function (key) {return data[key]}}}const c = createCache()c.set('a', 100)console.log(c.get('a'))
3、this
this 取值是在函数执行时确定(谁调用我,我就指向谁),常用场景有:
- 作为普通函数调用:直接返回 window
- 使用 call、apply、bind:改变了 this 的指向
- 作为对象方法被调用:返回对象本身
- 在 class 方法中调用:返回实例本身
- 箭头函数:箭头函数中的 this,是取上级作用域的 this
3.1 手写 call 函数
Function.prototype.myCall = function (context = window, ...args) {context.fn = thisconst result = context.fn(...args)delete context.fnreturn result}
3.2 手写 apply 函数
Function.prototype.myApply = function (context = window, args) {context.fn = thisconst result = !args ? context.fn() : context.fn(...args)delete context.fnreturn result}
3.3 手写 bind 函数
Function.prototype.myBind = function (context, ...args) {const self = thisreturn function () {return self.apply(context, args)}}
四、异步
JS 是单线程语言,只能同时做一件事,但遇到等待(网络请求,定时任务)时不能卡住,阻塞代码执行,因此需要异步的机制,异步是基于 callback 回调的方式去执行的。
应用场景:网络请求(ajax、图片加载等)、定时任务等
1、Promise
Promise 是异步编程的一个解决方案,比传统的回调函数解决方案更加合理和强大
1.1 基础用法
下面是一个图片加载的例子:
function loadImg(src) {return new Promise((resolve, reject) => {const img = document.createElement('img')img.onload = () => {resolve(img)}img.onerror = () => {const err = new Error(`图片加载失败 ${src}`)reject(err)}img.src = src})}const url1 = 'url1'const url2 = 'url2'// then、catch 方法均会返回一个Promise对象loadImg(url1).then(img1 => {console.log(img1.width)return img1 // 普通对象}).then(img1 => {console.log(img1.height)return loadImg(url2) // promise 实例}).then(img2 => {console.log(img2.width)return img2}).then(img2 => {console.log(img2.height)}).catch(err => console.error(err))
1.2 进阶知识
promise 有三种状态:
- pending:正在过程中,还没结果;不会触发 then 和 catch
- resolved:已经解决,成功了;会触发后续的 then 回调函数
- rejected:已经拒绝,失败了;会触发后续的 catch 回调函数
状态转换只能由 pending -> resolved 或 pending -> rejected,变化不可逆
then 和 catch 改变状态的方式:
- then 正常返回 resolved,里面有报错则返回 rejected
- catch 正常返回 resolved,里面有报错则返回 rejected ```javascript // 例子一 Promise.resolve().then(() => { console.log(1) }).catch(() => { console.log(2) }).then(() => { console.log(3) }) // 1 3
// 例子二 Promise.resolve().then(() => { console.log(1) throw new Error(‘erro1’) }).catch(() => { console.log(2) }).then(() => { console.log(3) }) // 1 2 3
// 例子三 Promise.resolve().then(() => { console.log(1) throw new Error(‘erro1’) }).catch(() => { console.log(2) }).catch(() => { // 注意这里是 catch console.log(3) }) // 1 2
```<a name="UGwA7"></a>### 2、Event Loop(事件循环/事件轮询)Event Loop 是异步回调的实现原理,如图所示<br /><br />其中- Call Stack:调用栈,触发执行代码- Web APIs:触发如定时器、异步等 Web API- Event Loop:循环机制,若调用栈为空,则轮询将回调队列中的函数放入调用栈中执行- Callback Queue:回调队列,异步操作结束,会将回调函数推入回调队列中,等待执行执行过程如下:1. 同步代码,一行一行放在 Call Stack 执行2. 遇到异步,会先 “记录” 下,等待时机(定时、网络请求)3. 时机到了,就移动到 Callback Queue4. 如果 Call stack 为空(即同步代码执行完)- 先执行当前微任务- 再尝试 DOM 渲染- 最后触发 Event Loop 开始工作5. 轮询查找 Callback Queue,如有则移动到 Call Stack 执行6. 然后继续轮询查找<a name="xq6t5"></a>### 3、async/awaitasync/await 是个语法糖,是用同步写法,去编写异步代码<a name="k7aNr"></a>#### 3.1 基础用法下面是一个图片加载的例子:```javascriptfunction loadImg(src) {return new Promise((resolve, reject) => {const img = document.createElement('img')img.onload = () => {resolve(img)}img.onerror = () => {const err = new Error(`图片加载失败 ${src}`)reject(err)}img.src = src})}const url1 = 'url1'const url2 = 'url2';(async function () {const img1 = await loadImg(url1)console.log(img1.height, img1.width)const img2 = await loadImg(url2)console.log(img2.height, img2.width)})()
3.2 进阶知识
- 执行 async 函数,返回的是 Promise 对象
- await 相当于 Promise 的 then
- try…catch 可捕获异常,代替了 Promise 的 catch
看一个执行顺序的问题:
async function async1() {console.log('async1 start') // 2await async2()// await 的后面,都可以看作是 callback 里的内容,即异步console.log('async2 end') // 5}async function async2() {console.log('async2') // 3}console.log('script start') // 1async1()console.log('script end') // 4
4、宏任务/微任务
- 宏任务(macroTask):在 DOM 渲染后触发,如:setTimeout、setInterval、Ajax、DOM事件
- 微任务(microTask):在 DOM 渲染前触发,如:Promise、async/await
区别:微任务执行时机比宏任务要早(根据 Event Loop 执行机制,各自在 DOM 渲染前后触发),可在控制台输以下代码体验效果
document.children[0].innerHTML = '你好像个憨憨'Promise.resolve().then(() => {alert('Promise then 已经执行') // Dom 未渲染})setTimeout(() => {alert('setTimeout 已经执行') // Dom 已渲染})
原因:
- 宏任务会经过调用 Web APIs 后,再进入回调队列(Callback Queue)
- 微任务不会经过 Web APIs,如 promise,它的回调是进入微任务队列(Micro Task Queue),在 Dom 渲染前执行(因为微任务是 ES6 语法规定,宏任务是由浏览器规定的)
5、异步综合面试题
执行顺序问题
async function async1 () {console.log('async1 start')await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务)}async function async2 () {console.log('async2')}console.log('script start')setTimeout(function () { // 异步,宏任务console.log('setTimeout')}, 0)async1()// 初始化 promise 时,传入的函数会立即执行new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码console.log('promise1') // Promise 的函数体会立刻执行resolve()}).then (function () { // 异步,微任务console.log('promise2')})console.log('script end')// 同步代码执行完毕(event loop - call stack 被清空)// 执行微任务// (尝试触发 Dom 渲染)// 触发 Event Loop,执行宏任务// script start// async1 start// async2// promise1// script end// async1 end// promise2// setTimeout
