变量类型和计算

题目

  1. typeof能判断那些类型
  2. 何时使用===,何时使用==
  3. 值类型和引用类型的区别
  4. 手写深拷贝

知识点

  • 变量类型
    • 值类型
      • 分类:undefined、String、Number、Boolean、Symbol
      • let定义的变量,默认为undefined
      • const定义的变量,必须要有赋值操作,且无默认值
    • 引用类型
      • 分类:对象字面量、数组、null(指针指向为空地址)、function fn() {}
      • 特殊引用类型:null、函数,不用于存储数据,所有没有“拷贝、复制函数”
      • 会有干扰的现象,本质是因为kv在key和value对应之间其实有一个内存地址做索引。
  • 变量计算
    • 类型转换

考察
1.typeof运算符

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)
    1. // 识别所有值类型
    2. let a;
    3. typeof a // 'undefined'
    4. typeof 'abc' // 'string'
    5. typeof 123 // 'number'
    6. typeof true // 'boolean'
    7. typeof Symbol('a') // 'symbol'
    8. // 识别函数
    9. typeof console.log // 'function'
    10. typeof function () {} // 'function'
    11. // 判断是否是引用类型(不可再细分)
    12. typeof null // 'object'
    13. typeof [] // 'object'
    14. typeof {} // 'object'

2.深拷贝

  • 考察点:
    • 判断值类型和引用类型
    • 判断是数组还是对象
    • 递归
  • 应用了typeof和instanceof
  • 应用了for in;对于对象会获取key,对于数组会获取下标

    1. // 使用递归实现深度拷贝
    2. function deepClone(obj = {}) {
    3. if (typeof obj !== 'object' || obj == null) {
    4. // obj 不是对象和数组,或者obj是null(也包括undifined),否者直接返回(所有值类型和函数)
    5. return obj
    6. }
    7. // 初始化返回结果
    8. let result
    9. if (obj instanceof Array) {
    10. result = []
    11. } else {
    12. result = {}
    13. }
    14. for (let key in obj) {
    15. // for...in会遍历实例和原型上所有属性和方法
    16. // hasOwnProperty保证key不是原型的属性
    17. if (obj.hasOwnProperty(key)) {
    18. result[key] = deepClone(obj[key])
    19. }
    20. }
    21. // 返回结果
    22. return result
    23. }

    备注:

  • Object.keys(obj) 不会获取obj原型上的属性

  1. const d = true + 10 // 11

另外一种判断类型的方法,就是Object.prototype.toString

  1. // 返回结果包括[object Number]、[object String]、[object Boolean]、
  2. // [object Object]、 [object Array]、[object Function]
  3. // [object Null]、[object Undefined]、[object Symbol]
  4. Object.prototype.toString.call(Symbol(1)) // [object Symbol]

原型和原型链

JavaScript是基于原型继承的语言,ES5的class本质上还是原型的继承。
题目

  1. 如何准确判断一个变量是不是数组
  • a instanceof Array
  1. 手写一个简易的jQuery,考虑插件和扩展性
  2. class的原型本质,怎么理解?
  • 原型和原型链的图示

知识点

  • class和继承
    • class就是一个包含 constructor & 属性 & 方法 的模版
    • 继承
      • extends
      • super
      • 扩展和重写方法
  • instanceof(类型判断)的本质
    • instanceof可以判断引用类型,即是不是被后者构建出来的
    • Object是所有class的父类 ```javascript student instanceof Student // true student instanceof Person // true student instanceof Object // true

[] instanceof Array // true [] instanceof Object // true {} instanceof Object // true

  1. - 原型和原型链
  2. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/173394/1616207372334-247c9d58-3e09-4820-870d-d907f62837b0.png#height=254&id=N58Yt&margin=%5Bobject%20Object%5D&name=image.png&originHeight=508&originWidth=1178&originalType=binary&size=137634&status=done&style=stroke&width=589)<br />**图1** 各个对象之间的关系<br />一个新函数会自动创建一个prototype属性。<br />在图1展示了 Person构造函数、Person的原型以及Person现有的两个实例之间的关系。可见,**实例**与**构造函数**没有直接的关系。<br />类的**prototype**(显示原型)和实例的**__proto__**(隐式原型)都指向函数的原型对象(**Prototype**)。<br />**实例.属性**,会先在**实例**上找,如果没找到则在**原型**上找。
  3. ```javascript
  4. // class 实际上是函数(语法糖)
  5. typeof People // 'function'
  6. typeof Student // 'function'
  7. // 隐式原型和显示原型
  8. student.__proto__ === Student.prototype // true
  1. // ES6的class
  2. class Person {
  3. static kind = '人' // 类属性
  4. constructor(name, age) {
  5. this.name = name // 实例属性
  6. this.age = age
  7. }
  8. static grow() {} // 类方法
  9. eat() {} // 实例方法
  10. }
  11. // 对应的语法糖
  12. var Person = function () {
  13. function Person(name, age) {
  14. this.name = name
  15. this.age = age
  16. }
  17. Person.grow = function () {}
  18. Person.prototype.eat = function () {}
  19. Person.kind = '人'
  20. return Person
  21. }()
  22. // hasOwnProperty判断是否是“实例的属性”
  23. let person = new Person('Allen', 18)
  24. person.hasOwnProperty('name') // true
  25. person.hasOwnProperty('eat') // false,因为是原型的属性

以上可知,class的实例属性和方法

原型链
image.png
Object.prototype.proto是null,即Object不能向上找了

  1. function SuperType(){
  2. this.property = true
  3. }
  4. SuperType.prototype.getSuperValue = function(){
  5. return this.property
  6. }
  7. function SubType(){
  8. this.subproperty = false
  9. }
  10. //继承了 SuperType
  11. SubType.prototype = new SuperType()
  12. SubType.prototype.getSubValue = function (){
  13. return this.subproperty
  14. }
  15. var instance = new SubType()
  16. console.log(instance.getSuperValue()); //true

确定原型和实例之间的关系有2种方式
方式1:instanceof操作符

  1. // 是后者的实例
  2. instance instanceof Object // true
  3. instance instanceof SuperType // true
  4. instance instanceof SubType // true

方式2: isPrototypeOf()方法

  1. // 是后者的原型
  2. Object.prototype.isPrototypeOf(instance) // true
  3. SuperType.prototype.isPrototypeOf(instance) // true
  4. SubType.prototype.isPrototypeOf(instance) // true

作用域和闭包

JavaScript中的this依赖于函数的调用方式,因此把this称为调用上下文。

手写bind函数

用apply+闭包实现bind函数

  1. // 第1版
  2. Function.prototype.newBind = function () {
  3. // arguments默认获取传入的参数;this在函数执行时确定
  4. // fn(1,2,3) 中arguments为 Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  5. // fn1.newBind调用了,因此此处的this就是self
  6. // 在定义时(闭包的问题)固定了运行时(this指向的问题)的参数
  7. const self = this
  8. const args = Array.prototype.slice.call(arguments)
  9. const context = args.shift() || window
  10. return function () {
  11. // 此处的this为window,所以需要在外层保存上层作用域的this
  12. // 涉及到闭包
  13. return self.apply(context, args)
  14. }
  15. }
  16. // 第2版(简化)
  17. Function.prototype.newBind = function (context, ...args) {
  18. const self = this
  19. return function () {
  20. return self.apply(context, args)
  21. }
  22. }
  23. // 第3版(简化)
  24. Function.prototype.newBind = function (context, ...args) {
  25. return () => this.apply(context, args)
  26. }
  27. function fn1(arg1, arg2, arg3) {
  28. console.log('this:', this)
  29. console.log('args:', arg1, arg2, arg3)
  30. }
  31. const fn2 = fn1.newBind({x: 0}, 1, 2, 3)
  32. fn2() // 执行

知识点

  • 函数体内可以通过 arguments对象来访问这个参数数组

    异步

    JS的执行概念

    JS 是一步一步从前到后执行,如果某一行执行错误则停止下面代码的执行。
    JS 先执行同步代码完毕,再执行异步。
    JS 是单线程运行的。

异步的应用场景

  • 网络请求,如ajax图片加载
  • 定时任务,如setTImeout
  • I/O操作,如数据库查询

    Promise

    Promise的三种状态:pending、resolved、 rejected。
    两种状态变化,且变化不可逆:

  • pending状态,不会触发then和catch;

  • resolved状态,会触发后续的then回调函数;
  • rejected状态,会触发后续的catch回调函数。

then和catch对状态的影响:

  • then正常返回resolved,里面有报错则返回rejected;
  • catch正常也会返回resolved,里面有报错则返回rejected。

注意:
Promise的执行细节

  1. // 会先运行完Promise.resolve()
  2. // 再将() => console.log('sync')放入微任务重
  3. Promise.resolve().then(() => console.log('sync'))
  4. // 初始化 Promise 时,传入的函数会立即被执行
  5. // resolve()不会阻塞同级作用域代码的执行
  6. // 如下代码的执行结果就是1、2、3、4
  7. new Promise(function (resolve) {
  8. console.log('promise1')
  9. resolve()
  10. console.log('promise2')
  11. }).then(function () {
  12. console.log('promise4')
  13. })
  14. console.log('promise3')

手写Promise加载图片

  1. function loadImg(src) {
  2. return new Promise((resolve, reject) => {
  3. const img = document.createElement('img')
  4. img.onload = () => {
  5. // 返回 <img src="XXX">这个DOM的Promise
  6. resolve(img)
  7. }
  8. img.onerror = () => {
  9. const err = new Error(`图片加载失败:${src}`)
  10. reject(err)
  11. }
  12. img.src = src
  13. })
  14. }
  15. const imgUrl = 'https://gw.alipayobjects.com/mdn/prod_resou/afts/img/A*OwZWQ68zSTMAAAAAAAAAAABkARQnAQ'
  16. loadImg(imgUrl).then(img => {
  17. console.log('img', img.width, img.height)
  18. }).catch(err => console.log('err', err))

async/await

原理

async/await 改变不了JS单线程的本质,本质上还是基于event loop的异步。
async/await 消灭了异步回调,但和Promise并不互斥,而是相辅相成。
关键点:

  • 执行 async 函数,返回的是Promise对象。
  • await 相当于 Promise的then,可以理解为:会把同级作用域下的await后的所有一行行的代码放在then中作为回调函数。
  • try… catch可捕获异常,代替了Promise的catch。

注意:
同步代码执行完毕,才执行event loop。
async 操作符并不会阻塞代码的执行;
await 操作符也不会阻塞它同行的代码的执行;
关键是,await对于它同级作用域下面的代码的影响。

  1. async function async1() {
  2. console.log('async1 start') // 2
  3. await async2
  4. console.log('async1 end') // 5
  5. }
  6. async function async2() {
  7. console.log('async2') // 3
  8. }
  9. console.log('script start') // 1
  10. async1
  11. console.log('script end') // 4

同步与异步遍历

  • for…in(forEach、for)是常规的同步遍历
  • for…of 是异步遍历

Event Loop 的机制

名词定义

  • Call Stack(调用栈)
  • Web APIs(Dom操作、定时器等(原生js没有的))
  • Callback Queue(回调函数的队列)
  • Event Loop(事件轮询)

Event Loog 就是异步回调的实现原理。

问题:基于以上4个名词,用【图】描述下方代码的Event Loop 机制

  1. console.log('Stpe 1')
  2. setTimeout(function cb1() {
  3. console.log('cb1')
  4. }, 5000)
  5. console.log('Stpe 2')

解答:
分为2个大步骤:同步代码执行完毕、开始执行异步代码。
如果异步代码中也有同步代码,则重复如上的大步骤。

DOM事件和Event Loop的关系

原理:
当执行到 $('#btn').click时,会将内部的回调函数放在 宏任务中,什么时候用户点击了就将回调函数放在 Callback Queue中,等待Event Loop轮询。
DOM事件是由用户来触发的,与其他异步操作的不同点是触发机制不同。
DOM事件使用了回调,但不是异步

宏任务与微任务

  • 宏任务(macroTask):setTimeout、setInterval、Ajax、DOM事件
  • 微任务(microTask):Promise、async/await

微任务执行时机先于宏任务

宏任务与微任务的区别

  • 宏任务:DOM渲染后出发,如setTimeout
  • 微任务:DOM渲染前出发,如Promise

JS是单线程,并且和 DOM 渲染共用一个线程。
JS执行时,需要留一些时机供 DOM 渲染,顺序如下:
1、Call Stack 空闲
2、先尝试DOM渲染
3、再触发Event Loop

不同的原因

微任务是ES6语法规定的,宏任务是由浏览器规定(W3C)的,所以「微任务和宏任务」的任务执行时存放的位置不同。
在ES6语法中找不到onclick事件、Ajax方法。
JS执行时的顺序如下:
1、Call Stack 空闲
2、执行当前的微任务,从micro tast queue
3、尝试DOM渲染
4、再触发Event Loop,从macro tast queue(即Web APIs)

Web-API

Web-API:DOM

DOM 是 Document Object Model(文档对象模型)的缩写

Web-API:BOM

Web-API:事件

事件绑定

事件冒泡

事件代理

Web-API:Ajax

手写Ajax

get请求和post请求

  1. // 在http://47.114.33.143:9000 页面下的console中执行,查看效果
  2. // POST请求
  3. const xhr = new XMLHttpRequest()
  4. //xhr.open('GET', '/cms/banner/1', true) // 也可以
  5. xhr.open('GET', 'http://47.114.33.143:9000/cms/banner/1', true)
  6. xhr.setRequestHeader('Content-Type','application/json') // 可以省略
  7. xhr.onreadystatechange = function () {
  8. // 此处的代码为异步执行
  9. if(xhr.readyState === 4) {
  10. if (xhr.status === 200) {
  11. console.log(JSON.parse(xhr.responseText))
  12. } else {
  13. console.log('其他情况')
  14. }
  15. }
  16. }
  17. xhr.send(null)
  18. // POST请求
  19. const xhr = new XMLHttpRequest()
  20. xhr.open('POST', '/v1/token', true)
  21. xhr.setRequestHeader('Content-Type','application/json')
  22. xhr.onreadystatechange = function () {
  23. // 此处的代码为异步执行
  24. if(xhr.readyState === 4) {
  25. if (xhr.status === 200) {
  26. console.log(JSON.parse(xhr.responseText))
  27. } else {
  28. console.log('其他情况')
  29. }
  30. }
  31. }
  32. const postData = {
  33. account: '999@qq.com',
  34. secret: '123456',
  35. type: 101
  36. }
  37. xhr.send(JSON.stringify(postData))

Web-API:存储

cookie

  • cookie是http请求的一部分,本身用于浏览器和server端通讯
  • 被“借用”到本地存储
  • 可以用document.cookie= 'xxx=yyyy'来修改,且写法是逐一添加与修改

缺点:

  • 存储太小,最大4KB(本身设计就不是存储的)
  • http请求时需要发送到服务端,会增加http请求发送的数据量
  • 只能用document.cookie= 'xxx=yyyy'来修改,过于简陋难用

    localStorage、sessionStorage比较

    优点:

  • HTML5专门为存储而设计的,最大可存5M

  • API简单易用setItem、getItem
  • 不会随着http请求被发送出去

区别:

  • localStorage 数据会永久存储,除非代码或手动删除
  • sessionStorage 数据只存在于当前会话,浏览器关闭则清空
  • 一般用localStorage会更多一些

用法

  1. localStorage.setItem('a', 100)
  2. localStorage.getItem('a')
  3. sessionStorage.setItem('b', 100)
  4. sessionStorage.getItem('b')

HTTP

RESTFul-API

  • 把每个url当作一个唯一的资源
  • 常用的方法:get、post、patch/put、delete

常见的HTTP状态码

状态码分类

  • 1xx 服务器收到请求
  • 2xx 请求成功
  • 3xx 重定向
  • 4xx 客户端错误(请求包含语法错误或无法完成请求)
  • 5xx 服务端错误

    常见状态码

  • 200 请求成功

  • 301 永久重定向(配合location,浏览器自动处理;浏览器永远不会再访问)
  • 302 临时重定向(配合location,浏览器自动处理;浏览器只是临时一次)
    • 应用“短网址”
  • 304 资源未被修改(已经请求过了,资源在本地有了,就不给你了)
  • 400 错误的请求(一般用于参数错误)
  • 401 未授权(授权失败)Unauthorized
  • 403 没有权限访问 Forbidden
    • 应用“访问权限判断”
  • 404 资源未找到
  • 500 服务器错误
  • 504 网关超时(服务器处理太长了:E.g: 数据库查询)

HTTP常见header

  • Request Headers
  • Response Headers

HTTP缓存

HTTP缓存策略

  • 强制缓存 :浏览器直接从本地缓存中获取数据,不与服务器进行交互。
  • 协商缓存: 浏览器发送请求到服务器,服务器判定是否可使用本地缓存。

联系与区别:

  • 两种缓存方式最终使用的都是本地缓存;
  • 前者无需与服务器交互,后者需要。

    强制缓存

    cache-control 已经替代了 expires 去控制缓存过期。
    cache-control 的值:

  • max-age

  • no-cache 防止从缓存中获取过期的资源
  • no-store 不进行缓存
  • privae
  • public

协商缓存

Request Headers 可以带 Etag 或且 Last-Modified字段
Respose Headers 带 If-None-Match 或且 If-Modified-Since 字段
协商缓存判断成功,会返回304(资源未被修改)

image.png
图 HTTP缓存流程图

刷新页面对http缓存的影响

三种刷新操作:
1、正常操作: 地址栏输入URL,跳转链接,前进后退等

  • 强制缓存有效
  • 协商缓存有效

2、手动刷新: F5

  • 强制缓存失效
  • 协商缓存有效

3、强制刷新:ctrl + F5

  • 强制缓存失效
  • 协商缓存失效