变量类型和计算
题目
- typeof能判断那些类型
- 何时使用===,何时使用==
- 值类型和引用类型的区别
- 手写深拷贝
知识点
- 变量类型
- 值类型
- 分类:undefined、String、Number、Boolean、Symbol
- let定义的变量,默认为undefined
- const定义的变量,必须要有赋值操作,且无默认值
- 引用类型
- 分类:对象字面量、数组、null(指针指向为空地址)、function fn() {}
- 特殊引用类型:null、函数,不用于存储数据,所有没有“拷贝、复制函数”
- 会有干扰的现象,本质是因为kv在key和value对应之间其实有一个内存地址做索引。
- 值类型
- 变量计算
- 类型转换
考察
1.typeof运算符
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
// 识别所有值类型
let a;
typeof a // 'undefined'
typeof 'abc' // 'string'
typeof 123 // 'number'
typeof true // 'boolean'
typeof Symbol('a') // 'symbol'
// 识别函数
typeof console.log // 'function'
typeof function () {} // 'function'
// 判断是否是引用类型(不可再细分)
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
2.深拷贝
- 考察点:
- 判断值类型和引用类型
- 判断是数组还是对象
- 递归
- 应用了typeof和instanceof
应用了for in;对于对象会获取key,对于数组会获取下标
// 使用递归实现深度拷贝
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 不是对象和数组,或者obj是null(也包括undifined),否者直接返回(所有值类型和函数)
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// for...in会遍历实例和原型上所有属性和方法
// hasOwnProperty保证key不是原型的属性
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
备注:
Object.keys(obj) 不会获取obj原型上的属性
const d = true + 10 // 11
另外一种判断类型的方法,就是Object.prototype.toString
// 返回结果包括[object Number]、[object String]、[object Boolean]、
// [object Object]、 [object Array]、[object Function]
// [object Null]、[object Undefined]、[object Symbol]
Object.prototype.toString.call(Symbol(1)) // [object Symbol]
原型和原型链
JavaScript是基于原型继承的语言,ES5的class本质上还是原型的继承。
题目
- 如何准确判断一个变量是不是数组
- a instanceof Array
- 手写一个简易的jQuery,考虑插件和扩展性
- 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
- 原型和原型链
![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 />**实例.属性**,会先在**实例**上找,如果没找到则在**原型**上找。
```javascript
// class 实际上是函数(语法糖)
typeof People // 'function'
typeof Student // 'function'
// 隐式原型和显示原型
student.__proto__ === Student.prototype // true
// ES6的class
class Person {
static kind = '人' // 类属性
constructor(name, age) {
this.name = name // 实例属性
this.age = age
}
static grow() {} // 类方法
eat() {} // 实例方法
}
// 对应的语法糖
var Person = function () {
function Person(name, age) {
this.name = name
this.age = age
}
Person.grow = function () {}
Person.prototype.eat = function () {}
Person.kind = '人'
return Person
}()
// hasOwnProperty判断是否是“实例的属性”
let person = new Person('Allen', 18)
person.hasOwnProperty('name') // true
person.hasOwnProperty('eat') // false,因为是原型的属性
以上可知,class的实例属性和方法
原型链
Object.prototype.proto是null,即Object不能向上找了
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property
}
function SubType(){
this.subproperty = false
}
//继承了 SuperType
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function (){
return this.subproperty
}
var instance = new SubType()
console.log(instance.getSuperValue()); //true
确定原型和实例之间的关系有2种方式
方式1:instanceof操作符
// 是后者的实例
instance instanceof Object // true
instance instanceof SuperType // true
instance instanceof SubType // true
方式2: isPrototypeOf()方法
// 是后者的原型
Object.prototype.isPrototypeOf(instance) // true
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
作用域和闭包
JavaScript中的this依赖于函数的调用方式,因此把this称为调用上下文。
手写bind函数
用apply+闭包实现bind函数
// 第1版
Function.prototype.newBind = function () {
// arguments默认获取传入的参数;this在函数执行时确定
// fn(1,2,3) 中arguments为 Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// fn1.newBind调用了,因此此处的this就是self
// 在定义时(闭包的问题)固定了运行时(this指向的问题)的参数
const self = this
const args = Array.prototype.slice.call(arguments)
const context = args.shift() || window
return function () {
// 此处的this为window,所以需要在外层保存上层作用域的this
// 涉及到闭包
return self.apply(context, args)
}
}
// 第2版(简化)
Function.prototype.newBind = function (context, ...args) {
const self = this
return function () {
return self.apply(context, args)
}
}
// 第3版(简化)
Function.prototype.newBind = function (context, ...args) {
return () => this.apply(context, args)
}
function fn1(arg1, arg2, arg3) {
console.log('this:', this)
console.log('args:', arg1, arg2, arg3)
}
const fn2 = fn1.newBind({x: 0}, 1, 2, 3)
fn2() // 执行
知识点
- 函数体内可以通过 arguments对象来访问这个参数数组
异步
JS的执行概念
JS 是一步一步从前到后执行,如果某一行执行错误则停止下面代码的执行。
JS 先执行同步代码完毕,再执行异步。
JS 是单线程运行的。
异步的应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTImeout
-
Promise
Promise的三种状态:pending、resolved、 rejected。
两种状态变化,且变化不可逆: pending状态,不会触发then和catch;
- resolved状态,会触发后续的then回调函数;
- rejected状态,会触发后续的catch回调函数。
then和catch对状态的影响:
- then正常返回resolved,里面有报错则返回rejected;
- catch正常也会返回resolved,里面有报错则返回rejected。
注意:
Promise的执行细节
// 会先运行完Promise.resolve()
// 再将() => console.log('sync')放入微任务重
Promise.resolve().then(() => console.log('sync'))
// 初始化 Promise 时,传入的函数会立即被执行
// resolve()不会阻塞同级作用域代码的执行
// 如下代码的执行结果就是1、2、3、4
new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise2')
}).then(function () {
console.log('promise4')
})
console.log('promise3')
手写Promise加载图片
function loadImg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
// 返回 <img src="XXX">这个DOM的Promise
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败:${src}`)
reject(err)
}
img.src = src
})
}
const imgUrl = 'https://gw.alipayobjects.com/mdn/prod_resou/afts/img/A*OwZWQ68zSTMAAAAAAAAAAABkARQnAQ'
loadImg(imgUrl).then(img => {
console.log('img', img.width, img.height)
}).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对于它同级作用域下面的代码的影响。
async function async1() {
console.log('async1 start') // 2
await async2
console.log('async1 end') // 5
}
async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1
async1
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 机制
console.log('Stpe 1')
setTimeout(function cb1() {
console.log('cb1')
}, 5000)
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请求
// 在http://47.114.33.143:9000 页面下的console中执行,查看效果
// POST请求
const xhr = new XMLHttpRequest()
//xhr.open('GET', '/cms/banner/1', true) // 也可以
xhr.open('GET', 'http://47.114.33.143:9000/cms/banner/1', true)
xhr.setRequestHeader('Content-Type','application/json') // 可以省略
xhr.onreadystatechange = function () {
// 此处的代码为异步执行
if(xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
} else {
console.log('其他情况')
}
}
}
xhr.send(null)
// POST请求
const xhr = new XMLHttpRequest()
xhr.open('POST', '/v1/token', true)
xhr.setRequestHeader('Content-Type','application/json')
xhr.onreadystatechange = function () {
// 此处的代码为异步执行
if(xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
} else {
console.log('其他情况')
}
}
}
const postData = {
account: '999@qq.com',
secret: '123456',
type: 101
}
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会更多一些
用法
localStorage.setItem('a', 100)
localStorage.getItem('a')
sessionStorage.setItem('b', 100)
sessionStorage.getItem('b')
HTTP
RESTFul-API
- 把每个url当作一个唯一的资源
- 常用的方法:get、post、patch/put、delete
常见的HTTP状态码
状态码分类
- 1xx 服务器收到请求
- 2xx 请求成功
- 3xx 重定向
- 4xx 客户端错误(请求包含语法错误或无法完成请求)
-
常见状态码
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(资源未被修改)
图 HTTP缓存流程图
刷新页面对http缓存的影响
三种刷新操作:
1、正常操作: 地址栏输入URL,跳转链接,前进后退等
- 强制缓存有效
- 协商缓存有效
2、手动刷新: F5
- 强制缓存失效
- 协商缓存有效
3、强制刷新:ctrl + F5
- 强制缓存失效
- 协商缓存失效