一、变量类型和计算

1. 值类型和引用类型

1.1 值类型

值类型变量存储在栈中(如:Undefined、String、Number、Boolean、Symbol)

1.2 引用类型

引用类型变量的内存地址存储在栈中,值存储在堆中(如:Object、Null、Array、Function)

1.3 浅拷贝

仅能拷贝指向值类型的对象属性,可使用 Object.assign({},obj) 或剩余运算符 {...obj}

1.4 深拷贝

彻底拷贝一个对象,对原对象无影响

  1. /**
  2. * 深拷贝
  3. * @param { Object } obj - 要拷贝的对象
  4. * @returns { Object } result - 拷贝后的副本
  5. */
  6. function deepClone(obj = {}) {
  7. if (typeof obj !== 'object' || obj == null) return obj
  8. let result
  9. if (obj instanceof Array) {
  10. result = []
  11. } else {
  12. result = {}
  13. }
  14. for(let key in obj) {
  15. if (obj.hasOwnProperty(key)) {
  16. result[key] = deepClone(obj[key])
  17. }
  18. }
  19. return result
  20. }

2. 类型判断

2.1 typeof 运算符

  • 能识别所有值类型
  • 能识别函数
  • 判断是否是引用类型(不可再细分)

3. 逻辑运算

逻辑运算中,javascript 会自动进行强制类型转换

3.1 字符串拼接

  • 数字 + 字符串 = 字符串
  • 布尔值 + 字符串 = 字符串

可使用 parseInt(string, radix) 和 Number(obj) 将字符串或对象转换为数字

  1. const num1 = parseInt('10.2') // parseInt 转化的是整数
  2. const num2 = Number('10.2') // 另一种语法糖写法:+'10.2'
  3. console.log(num1) // 10
  4. console.log(num2) // 10.2

3.2 == 运算符

双等号会进行强制类型转换,除了排除 null 和 undefined 外,建议都用 ===

  1. // 举例,以下均为 true
  2. 100 == 100
  3. 0 == ‘’
  4. 0 == false
  5. false == ''
  6. 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()

  1. <a name="NZYqg"></a>
  2. #### 1.2 继承
  3. 继承主要有以下关键点
  4. - extends(关键字)
  5. - super(执行父类的构建过程)
  6. - 扩展或重写方法
  7. ```javascript
  8. // 父类
  9. class People {
  10. constructor(name) {
  11. this.name = name
  12. }
  13. eat() {
  14. console.log(`${this.name} eat something`)
  15. }
  16. }
  17. // 子类
  18. class Student extends People {
  19. constructor(name, number) {
  20. super(name)
  21. this.number = number
  22. }
  23. sayHi() {
  24. console.log(`姓名:${this.name},学号:${this.number}`)
  25. }
  26. }
  27. // 子类
  28. class Teacher extends People {
  29. constructor(name, major) {
  30. super(name)
  31. this.major = major
  32. }
  33. teach() {
  34. console.log(`姓名:${this.name} 教授 ${this.major}`)
  35. }
  36. }
  37. const xialuo = new Student('夏洛', 100)
  38. console.log(xialuo.name)
  39. console.log(xialuo.number)
  40. xialuo.sayHi()
  41. xialuo.eat()
  42. const wanglaoshi = new Teacher('王老师', '语文')
  43. console.log(wanglaoshi.name)
  44. console.log(wanglaoshi.major)
  45. xialuo.sayHi()
  46. xialuo.eat()

2、类型判断 instanceof

判断是否通过该方法构建(new)出来的

  1. // 该例子见第一小节
  2. console.log(xialuo instanceof Student) // true
  3. console.log(xialuo instanceof People) // true
  4. console.log(xialuo instanceof Object) // true
  5. console.log([] instanceof Array) // true
  6. console.log([] instanceof Object) // true
  7. console.log({} instanceof Object) // true

3、原型和原型链

3.1 原型

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。

原型关系:

  • 每个 class 都有显式原型 prototype
  • 每个实例都有隐式原型 proto
  • 实例的 proto 指向对应 class 的prototype

基于原型的执行规则

  1. 获取属性或执行方法时,先在自身属性和方法找
  2. 如果找不到则自动去 proto 中去找
    1. // 该例子见第一小节
    2. // 隐式原型和显式原型
    3. console.log(xialuo.__proto__) // 隐式
    4. console.log(Student.prototype) // 显式
    5. console.log(xialuo.__proto__ === Student.prototype) // true
    原型.png

    3.2 原型链

    每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层最终指向 null,这就是原型链
    1. // 该例子见第一小节
    2. console.log(Student.prototype.__proto__) // 隐式
    3. console.log(People.prototype) // 显式
    4. console.log(Student.prototype.__proto__ === People.prototype) // true
    原型链.png

    三、作用域和闭包

    1、作用域和自由变量

    1.1 作用域

    作用域代表了一个变量合法的使用范围,包括:
  • 全局作用域
  • 函数作用域
  • 块级作用域

1.2 自由变量

自由变量即一个变量在当前作用域没有定义,但被使用了。它需要遵循下面的规则:

  1. 向上级作用域,一层一层依次寻找,直至找到为止
  2. 如果到全局作用域都没找到,则报错 xx is not defined

2、闭包

闭包是作用域应用的特殊情况,有两种表现:

  • 函数作为参数被传递
  • 函数作为返回值被返回

PS:

  • 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!
  • 下面两个例子返回值均为 100

闭包.png

2.1 常用场景

隐藏数据,实现简单的缓存工具

  1. // 闭包隐藏数据,只提供 API
  2. function createCache() {
  3. const data = {} // 闭包中的数据,被隐藏,不被外界访问
  4. return {
  5. set: function (key, val) {
  6. data[key] = val
  7. },
  8. get: function (key) {
  9. return data[key]
  10. }
  11. }
  12. }
  13. const c = createCache()
  14. c.set('a', 100)
  15. console.log(c.get('a'))

3、this

this 取值是在函数执行时确定(谁调用我,我就指向谁),常用场景有:

  • 作为普通函数调用:直接返回 window
  • 使用 call、apply、bind:改变了 this 的指向
  • 作为对象方法被调用:返回对象本身
  • 在 class 方法中调用:返回实例本身
  • 箭头函数:箭头函数中的 this,是取上级作用域的 this

3.1 手写 call 函数

  1. Function.prototype.myCall = function (context = window, ...args) {
  2. context.fn = this
  3. const result = context.fn(...args)
  4. delete context.fn
  5. return result
  6. }

3.2 手写 apply 函数

  1. Function.prototype.myApply = function (context = window, args) {
  2. context.fn = this
  3. const result = !args ? context.fn() : context.fn(...args)
  4. delete context.fn
  5. return result
  6. }

3.3 手写 bind 函数

  1. Function.prototype.myBind = function (context, ...args) {
  2. const self = this
  3. return function () {
  4. return self.apply(context, args)
  5. }
  6. }

四、异步

JS 是单线程语言,只能同时做一件事,但遇到等待(网络请求,定时任务)时不能卡住,阻塞代码执行,因此需要异步的机制,异步是基于 callback 回调的方式去执行的。
应用场景:网络请求(ajax、图片加载等)、定时任务等

1、Promise

Promise 是异步编程的一个解决方案,比传统的回调函数解决方案更加合理和强大

1.1 基础用法

下面是一个图片加载的例子:

  1. function loadImg(src) {
  2. return new Promise((resolve, reject) => {
  3. const img = document.createElement('img')
  4. img.onload = () => {
  5. resolve(img)
  6. }
  7. img.onerror = () => {
  8. const err = new Error(`图片加载失败 ${src}`)
  9. reject(err)
  10. }
  11. img.src = src
  12. })
  13. }
  14. const url1 = 'url1'
  15. const url2 = 'url2'
  16. // then、catch 方法均会返回一个Promise对象
  17. loadImg(url1).then(img1 => {
  18. console.log(img1.width)
  19. return img1 // 普通对象
  20. }).then(img1 => {
  21. console.log(img1.height)
  22. return loadImg(url2) // promise 实例
  23. }).then(img2 => {
  24. console.log(img2.width)
  25. return img2
  26. }).then(img2 => {
  27. console.log(img2.height)
  28. }).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

  1. ```
  2. <a name="UGwA7"></a>
  3. ### 2、Event Loop(事件循环/事件轮询)
  4. Event Loop 是异步回调的实现原理,如图所示<br />![event loop.png](https://cdn.nlark.com/yuque/0/2021/png/603415/1612297435650-f416d57c-b6b4-48b8-8fb1-21815de0232a.png#crop=0&crop=0&crop=1&crop=1&height=482&id=ZpeEC&margin=%5Bobject%20Object%5D&name=event%20loop.png&originHeight=964&originWidth=1550&originalType=binary&ratio=1&rotation=0&showTitle=false&size=233007&status=done&style=none&title=&width=775)<br />其中
  5. - Call Stack:调用栈,触发执行代码
  6. - Web APIs:触发如定时器、异步等 Web API
  7. - Event Loop:循环机制,若调用栈为空,则轮询将回调队列中的函数放入调用栈中执行
  8. - Callback Queue:回调队列,异步操作结束,会将回调函数推入回调队列中,等待执行
  9. 执行过程如下:
  10. 1. 同步代码,一行一行放在 Call Stack 执行
  11. 2. 遇到异步,会先 “记录” 下,等待时机(定时、网络请求)
  12. 3. 时机到了,就移动到 Callback Queue
  13. 4. 如果 Call stack 为空(即同步代码执行完)
  14. - 先执行当前微任务
  15. - 再尝试 DOM 渲染
  16. - 最后触发 Event Loop 开始工作
  17. 5. 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
  18. 6. 然后继续轮询查找
  19. <a name="xq6t5"></a>
  20. ### 3、async/await
  21. async/await 是个语法糖,是用同步写法,去编写异步代码
  22. <a name="k7aNr"></a>
  23. #### 3.1 基础用法
  24. 下面是一个图片加载的例子:
  25. ```javascript
  26. function loadImg(src) {
  27. return new Promise((resolve, reject) => {
  28. const img = document.createElement('img')
  29. img.onload = () => {
  30. resolve(img)
  31. }
  32. img.onerror = () => {
  33. const err = new Error(`图片加载失败 ${src}`)
  34. reject(err)
  35. }
  36. img.src = src
  37. })
  38. }
  39. const url1 = 'url1'
  40. const url2 = 'url2';
  41. (async function () {
  42. const img1 = await loadImg(url1)
  43. console.log(img1.height, img1.width)
  44. const img2 = await loadImg(url2)
  45. console.log(img2.height, img2.width)
  46. })()

3.2 进阶知识

  • 执行 async 函数,返回的是 Promise 对象
  • await 相当于 Promise 的 then
  • try…catch 可捕获异常,代替了 Promise 的 catch

看一个执行顺序的问题:

  1. async function async1() {
  2. console.log('async1 start') // 2
  3. await async2()
  4. // await 的后面,都可以看作是 callback 里的内容,即异步
  5. console.log('async2 end') // 5
  6. }
  7. async function async2() {
  8. console.log('async2') // 3
  9. }
  10. console.log('script start') // 1
  11. async1()
  12. console.log('script end') // 4

4、宏任务/微任务

  • 宏任务(macroTask):在 DOM 渲染后触发,如:setTimeout、setInterval、Ajax、DOM事件
  • 微任务(microTask):在 DOM 渲染前触发,如:Promise、async/await

区别:微任务执行时机比宏任务要早(根据 Event Loop 执行机制,各自在 DOM 渲染前后触发),可在控制台输以下代码体验效果

  1. document.children[0].innerHTML = '你好像个憨憨'
  2. Promise.resolve().then(() => {
  3. alert('Promise then 已经执行') // Dom 未渲染
  4. })
  5. setTimeout(() => {
  6. alert('setTimeout 已经执行') // Dom 已渲染
  7. })

原因:

  • 宏任务会经过调用 Web APIs 后,再进入回调队列(Callback Queue)
  • 微任务不会经过 Web APIs,如 promise,它的回调是进入微任务队列(Micro Task Queue),在 Dom 渲染前执行(因为微任务是 ES6 语法规定,宏任务是由浏览器规定的)

5、异步综合面试题

执行顺序问题

  1. async function async1 () {
  2. console.log('async1 start')
  3. await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
  4. console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 callback 的功能(微任务)
  5. }
  6. async function async2 () {
  7. console.log('async2')
  8. }
  9. console.log('script start')
  10. setTimeout(function () { // 异步,宏任务
  11. console.log('setTimeout')
  12. }, 0)
  13. async1()
  14. // 初始化 promise 时,传入的函数会立即执行
  15. new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
  16. console.log('promise1') // Promise 的函数体会立刻执行
  17. resolve()
  18. }).then (function () { // 异步,微任务
  19. console.log('promise2')
  20. })
  21. console.log('script end')
  22. // 同步代码执行完毕(event loop - call stack 被清空)
  23. // 执行微任务
  24. // (尝试触发 Dom 渲染)
  25. // 触发 Event Loop,执行宏任务
  26. // script start
  27. // async1 start
  28. // async2
  29. // promise1
  30. // script end
  31. // async1 end
  32. // promise2
  33. // setTimeout

五、模块化