1、JS值类型与引用类型的区别
typeof运算符?
· 识别所有的值类型
let a; typeof a // 'underfined'
let str = 'ass'; typeof str // 'string'
let n = 100; typeof n // 'number'
let flag = true typeof flag // 'boolean'
const s = Symbol('s') typeof s // 'symbol'
· 识别函数
typeof console.log('aaa'); // 'function'
typeof funciton(){}; // 'function'
· 判断是否是引用类型(不可再细分)
typeof null // 'object'
typeof ['a', 'b', 'c'] // 'object'
typeof {x: 100} // 'object'
2、深拷贝
手写一个 JS 深拷贝
· 注意判断值类型和引用类型
· 注意判读是否是数组还是对象
· 递归
function deepClone(obj = {}) {
// 如果不是对象直接返回
if (typeof obj !== 'object' || obj == null) {
return obj;
}
let result;
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key])
}
}
return result;
}
问题: 如何用堆栈解释深拷贝?
3、类型计算
字符串拼接
const a = 100 + 10 //110
const b = 100 + '10' // '10010'
改为这种形式就可以计算数值:
const b = 100 + parseInt('10') // 110
cosnt c = true + '10' // 'true10'
==
100 == ‘100’ // true
0 == ‘’ // true
0 == false // true
false == '' // true
null == undefined // true
如果有 == 来判断的话,以上都为true, == 会先把等号两边的类型转换为一致,然后再进行判断。
除了判断是否为空之外( xxx == null ),其他的一律用 ===, 例如: const obj = {a: '111'} if(obj.a == null) {}
== 判断是否为null, 与 === 同时判断是否为null 和是否为 undefined,效果一样。 例如: if(obj.a == null) {} 相当于 if(obj.a === null || obj.a === undefined) {}
if语句和逻辑运算
truely 变量: !!a === true 的变量; falsely变量: !!b === false 以下是falsely变量,除此之外都是truely变量: !!0 === false; !!'' === false; !!fasle === false; !!NaN === false; !!null === false; !!undefined === false;
console.log(123 && 0) // 0
console.log('' || '123') // '123'
console.log(!window.abc) // true
4、原型和原型链 (重)
JS本身基于原型来继承
class 和继承
class
· constructor
· 属性
· 方法
// 类
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
console.log(
`姓名 ${this.name} ,学号 ${this.number}`
)
}
}
// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()
继承
· extends
· super
· 扩展和重写方法
// 父类
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)
wanglaoshi.teach()
wanglaoshi.eat()
原型和原型链
class 实际上是函数,可见是语法糖:
tyoepf Person //'function' tyoeof Stydent // 'function'
隐式原型 和 显式原型:
console.log(xialuo.__proto__); console.log(Student.prototype);console.log(xialuo.__proto__ === Student.prototype)
原型
| Student | |
|---|---|
| prototype |
| Student.prototype | |
|---|---|
| sayHai() | 函数 |
| xailuo | |
|---|---|
| name | ‘夏洛’ |
| age | 20 |
| proto |
原型关系
每个class都有一个显式原型,每个实例都有一个隐式原型__proto__;
基于原型的执行规则:
获取属性 xialuo.name 或者执行方法 xialuo.sayHai(), 先在自身属性或方法中查找, 如果找不到则自动去 proto 中查找
原型链
类型判断instanceof
instanceof 是基于原型链实现的
面试题
1、如何准确判断一个变量是数组
a instanceof Array
2、class 的原型本质
· 原型和原型链的本质
· 属性和方法的执行规则
3、手写简易jQuery, 考虑插件和扩展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
5、作用域和闭包(重)
作用域和自由变量
作用域
· 全局作用域
· 函数作用域
· 块级作用域(es6新增)
自由变量
· 一个变量在当前作用域没有定义,但被使用了
· 向上级作用域,一层一层依次寻找,知道找到为止
· 如果到全局作用域都没有找到,则报错: xxx is not defined
· 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
闭包
· 闭包是作用域应用的特殊情况,有两种表现形式:
· 函数作为参数被传递
· 函数作为返回值被返回
[JS 闭包经典使用场景和含闭包必刷题](# juejin.cn/post/693746…)
this
this 在各个场景中取什么样的值,是在函数执行的时候确定的,不实在定义的时候确定的
1、作为普通函数被调用
2、使用call(直接调用call方法)、 bind(返回一个新的函数)、apply被调用
3、作为对象方法被调用
4、在class方法中被调用
5、箭头函数
箭头函数取值永远取它上级作用域的值,包括 this
# 详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景
面试题
1、this的不同应用场景,应该如何取值?
应用场景:
· 作为普通函数被调用
· 使用 call、bind、 apply
· 作为对象方法调用
· 在class方法中调用
· 箭头函数
this的取值
· 全局环境下,this 只想 window
· 一般函数的 this
全局环境下 this 指向 window; node 环境下 this 指向 global;
严格模式下, this 指向 underfined
· 作为对象方法的函数
· 构造器中的 this
2、手写bind函数?
Function.prototype.bind1 = function () {
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this (取出数组第一项,数组剩余的就是传递的参数)
const t = args.shift()
const self = this // 当前函数
// 返回一个函数
return function () {
// 执行原函数,并返回结果
return self.apply(t, args)
}
}
3、实际开发中闭包的应用场景,举例说明?
· 隐藏数据
· 如做一个简单的 catch 工具
// 闭包隐藏数据,只提供 API
function 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') )
4、创建10个 a 标签, 点击弹出序号
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
6、同步和异步
· 基于 JS 单线程语言
· 异步不会阻塞代码执行
· 同步会阻塞代码执行
单线程和异步
· JS是单线程语言,智能同时做一件事情
· 浏览器和nodejs已经支持 JS 启动进程,如 web socker
· JS 和 Dom渲染共用同一个线程,因为 JS 可修改 DOM 结构
· 遇到等待(网络请求,定时任务),不能卡住
· 需要异步
· 异步表现形式:回调 callback 函数形式
异步的应用场景
网络请求,如 ajax 图片加载
定时任务,setTimeout
callback hell 和 Promise
Promise解决了 callback hell 嵌套的问题
面试题
1、同步和异步的区别
同上
2、手写用Promise加载一张图片
function loadImg(src) {
const p = 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
}
)
return p
}
// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
// console.log(img.width)
// return img
// }).then(img => {
// console.log(img.height)
// }).catch(ex => console.error(ex))
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
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(ex => console.error(ex))
3、前端使用异步的场景有哪些?
同上
答案: 1、3、5、4、2
7、JS异步(进阶)
event loop (时间循环 / 事件轮询)
· JS 是单线程运行的
· 异步要基于回调来实现
· event loop 就是异步回调的实现原理
JS 是如何执行的?
· 从前到后,一行一行的执行
· 如果有一行代码执行错误,则停下下面代码的执行
· 先把同步代码执行完,再执行异步的代码
代码例子:
图示:
总结 event loop 过程
1、同步代码,一行一行放在 Call Stack 中执行
2、遇到异步,会先‘记录’下,等待时机(定时、网络请求等)
3、时机到了,就移动到 Callback Queue
4、如果 Callstack 为空(即同步代码执行完),Event Loop 开始工作
5、轮询查找 Callback Queue, 如果有则移动到 Call Stack 中
6、然后继续轮询查找(永动机一样)
DOM 事件和 event loop
· JS 是单线程
· 异步(setTimeout, ajax等)使用回调,基于 event loop
· DOM 事件也使用回调, 基于 event loop
Promise
三种状态
· pending、resolved、rejected
· pending => resolved 或者 pending => rejected
· 变化不可逆
状态的表现和变化
状态的表现
· pedding 状态,不会触发 then 和 catch
· resolved 状态,会触发后续的 then 回调函数
· rejected 状态,会触发后续的 catch 回调函数
then 和 catch 对状态的影响 (常考)
then 正常返回resolved, 里面有报错则返回 rejected
catch 正常返回resolved, 里面有报错则返回 rejected
结果打印: 1、3
解释: resolved 状态会执行then回调,所以首先会打印出 1 ,只有rejected状态才会执行 catch回调,所以不会打印 2 , 然后第一个then回调执行完后,没有报错还是 resolved 状态, 所以接着执行第二个 then 回调,打印出 3。
结果打印:1、2、3
解释: 首先执行then回调,打印 1,但是then回调中有报错,peomise状态变为 rejected 状态,rejected 状态会执行 catch 回调,打印 2,catch中没有报错,所以 Promise 是 resolved 状态,接着执行 then 回调,打印 3 。
结果打印: 1、2
解释: 首先执行 then 回调,打印 1,但是then回调中报错,promise状态变为rejected 状态,然后执行catch回调,打印出 2,catch中没有报错,还是resolved 状态,所以不会执行最后一个 catch回调。
async / await
· 异步回调 callback hell
· Promise then catch 链式调用,但也是基于回调函数
· async / await 是同步语法,彻底消灭回调函数
async/await 和 Promise 的关系
· async / await 是消灭异步回调的终极武器
· 但是和 Promise 并不互斥
· 反而,两者相辅相成
· 执行 async 函数,返回的是 Promise 对象
· await 相当于 Promise 的 then
· await 后面的代码会进入微任务队列
· try...catch可捕获异常,替代了 Promise 的 catch
异步的本质
· async / await 是消灭异步回调的终极武器
· JS 还是单线程,还是有异步,还是基于 event loop
· async / await 只是语法糖,但是真香
for ... of
· for ... in (以及forEach for)是常规的同步遍历
· for ... of 常用于异步的遍历
宏任务 macroTask 和 微任务 microTask
什么是宏任务? 什么是微任务
· 宏任务: setTimeout、setInterval、ajax 和 DOM 事件
· 微任务: Promise、async/await
· 微任务执行时机要比宏任务要早(先记住)
event loop 和 DOM 渲染
· 再次回顾一遍event loop 的过程
· JS 是单线程的,并且和 DOM 渲染共用一个线程
· JS 执行的时候,需要留一些时机供 DOM 渲染
· 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完
· 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
· 然后再去触发下一次 Event Loop
微任务和宏任务的区别
· 宏任务: DOM 渲染后触发,如setTimeout
· 微任务: DOM 渲染后触发,如 Promise
为什么微任务比宏任务的执行时间早?
· 微任务是 ES6 与法规定的
· 宏任务是浏览器规定的
问题
· 宏任务有哪些? 微任务有哪些? 微任务触发时机更早
· 微任务、宏任务和 DOM 渲染的关系?
· 微任务、宏任务和 DOM 渲染,在event loop中的过程 ?
面试题
描述event loop 的机制(可画图)?
什么是宏任务、什么是微任务?两者区别?
Promise 的三种状态,以及如何变化?
执行顺序的问题
async function async1 () {
console.log('async1 start') // 2
await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务) // 6
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeout(function () { // 异步,宏任务
console.log('setTimeout') // 8
}, 0)
async1()
new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
console.log('promise1') // Promise 的函数体会立刻执行 // 4
resolve()
}).then (function () { // 异步,微任务
console.log('promise2') // 7
})
console.log('script end') // 5
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务