JavaScript
ES6 新增那些东西?
- let 和 const 关键字
- 箭头函数
- 模板字符串
- 解构赋值
- 扩展运算符
- Promise 对象
- 类和继承
- 模块化
- 简化对象属性定义
说说 var、let、const 之间的区别
变量提升: var 会提升的变量的声明到当前作用域的顶部 let const 不会
暂时性死区:在代码块内,使用 let、const 命令声明变量或常量之前,该变量或常量都是不可用的。这在语法上,称为“暂时性死区”
块级作用域:var 没有块级作用域,let const 有块级作用域
重复声明: var 允许重复声明,let,const 不允许不允许在相同作用域内,重复声明同一个变量
修改声明的变量: var 和 let 可以修改;const 声明一个只读的常量,并且 const 声明时,必须立即初始化
window 对象的属性和方法:全局作用域中,var 声明的变量和通过 function 声明的函数,会自动变成 window 对象的属性或方法,let、 const 不会
null 和 undefined 的区别
undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义,null 表示"没有对象",即该处不应该有值
undefined 转为数值为 NaN,null 转为数值为 0
为什么 0.1+0.2!==0.3
JavaScript
是以 64
位双精度浮点数存储所有 Number
类型值,按照 IEEE754
规范,0.1 的二进制数只保留 52 位有效数字,即
;(1.100110011001100110011001100110011001100110011001101 * 2) ^ -4
同理,0.2 的二进制数为
;(1.100110011001100110011001100110011001100110011001101 * 2) ^ -3
复制代码
这样在进制之间的转换中精度已经损失。运算的时候如下
0.00011001100110011001100110011001100110011001100110011010
+0.00110011001100110011001100110011001100110011001100110100
------------------------------------------------------------
=0.01001100110011001100110011001100110011001100110011001110
那么如何让其相等呢?
- 将数字转成整数:
function add(num1, num2) {
const num1Digits = (num1.toString().split('.')[1] || '').length
const num2Digits = (num2.toString().split('.')[1] || '').length
const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits))
return (num1 * baseNum + num2 * baseNum) / baseNum
}
- 在 ES6 中新增了一个 Number.EPSILON 方法,它可以用来设置“能够接受的误差范围",只要判断 0.1+0.2-0.3 是否小于 Number.EPSILON,如果小于,就可以判断为 0.1+0.2===0.3
数据类型检测的方式有哪些
typeof
typeof 对于string
,boolean
,number
,undefined
,function
,symbol
等类型可正确判断
对于null
,array
,object
判断结果均为 object
特殊的对于 null
,null
不是一个对象,尽管 typeof null
输出的是 object
,这是一个历史遗留问题,JS
最初为了性能使用低位存储变量的 类型信息 ,000
开头代表是对象,null
表示为全零,所以将它错误的判断为 object
instanceof
instanceof 代码形式为object instanceof constructor
(object 是否是 constructor 的实例),该操作符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上(可用于判断引用类型,不能判断原始类型)
constructor <Badge text="不建议使用,原型可能被改变'/>
constructor
有两个作用,一是判断数据的类型,二是对象实例通过constructor
访问它的构造函数
console.log([].constructor === Array)
Object.prototype.toString.call()
数组
- Object.prototype.toString.call(obj).slice(8,-1) === 'Array'
- 原型链
obj.__proto__ === Array.prototype
- Array.isArray(obj)
- obj instanceof Array
- Array.prototype.isPrototypeOf(obj)
何时使用 === 何时使用 ==
除了 == null 之外 其它地方一律用===
const obj = { a: 2 }
if (obj.b == null) {
// 相当于 if(obj.b===null||obj.b===undefined)
console.log('b')
}
聊聊 JavaScript 中的数据类型
目前, JavaScript
原始类型有六种,分别为:
Boolean
String
Number
Undefined
Null
Symbol
(ES6 新增)
Symbol
是 JavaScript 中一种基本数据类型,它表示一种独一无二的数据类型,可以用来作为对象属性名或其他需要唯一标识符的场合。每个Symbol
都是独一无二的,即使两个Symbol
的描述字符串相同,它们也是不相等的。使用
Symbol
可以避免对象属性名的冲突,因为每个Symbol
都是唯一的,不会被覆盖或修改。同时,Symbol
也可以用来定义一些语言内部的特殊行为,比如实现迭代器或自定义对象的 toString()方法等。
ES10 新增了一种基本数据类型:BigInt
BigInt
类型通常用于需要处理大数的场景。JavaScript 的 Number 类型可以表示的最大整数是 2 的 53 次方减 1,也就是 9007199254740991。如果需要处理更大的整数,则可以使用BigInt
类型。由于 JavaScript 中的
Number
类型采用的是 IEEE 754 标准的双精度浮点数,其中只有 53 位用于表示整数部分,因此它只能表示有限的整数范围,超出范围的数字会失去精度或溢出。例如,当你声明一个非常大的数字时,它可能会被转换成科学计数法,这样就会导致丢失精度。另外,在进行复杂的计算时,也可能会出现精度误差的问题。因此,如果需要处理更大的整数,应该使用
BigInt
类型,而不是Number
类型,以确保数值的精度和正确性。一些使用的场景:
处理加密算法,例如 RSA 加密算法需要大整数运算。
处理与时间相关的操作,例如计算 Unix 时间戳,需要处理大于
Number.MAX_SAFE_INTEGER
的整数。处理与金额相关的操作,例如处理货币时可能需要使用到大数运算。
处理精度要求极高的计算,例如处理大型物理模拟或复杂科学计算等。
. 处理大型 ID 或其他需要大整数的标识符
引用类型只有一种: Object
null
不是一个对象,尽管 typeof null
输出的是 object
,这是一个历史遗留问题,JS
的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,null
表示为全零,所以将它错误的判断为 object
原始值和引用值的区别
内存的分配不同
- 原始值存储在栈中
- 引用值存储在堆中,栈中存储的变量,是指向堆中的引用地址
访问机制不同
- 原始值是按值访问
- 引用值按引用访问,JavaScript 不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象的值
复制变量时不同
- 原始值:a=b;是将 b 中保存的原始值的副本赋值给新变量 a,a 和 b 完全独立,互不影响
- 引用值:a=b;将 b 保存的对象内存的引用地址赋值给了新变量 a;a 和 b 指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变
比较变量时不同
原始值:==比较值是否相等(先进行类型转换再确定操作数是否相等---引自 js 高级程序设计(第四版) P71),===不仅比较值是否相等,还会比较数据类型是否相同
引用数据类型:不管是 == 还是 === ,都是比较内存地址是否相同,即比较是否都指向同一个对象
参数传递的不同
函数传参都是按值传递(栈中的存储的内容):原始值,拷贝的是值;引用值,拷贝的是引用地址
手写深拷贝
仅仅是解决了深复制的关键问题,还需要针对不同的数据类型进行完善,
lodash
的深拷贝针对不同的数据类型进行了处理,见深入浅出 loadash 深拷贝源码
copyObjs.push({ target, copyTarget: obj }) 这一步为什么要放在递归遍历之前进行
这一步需要放在递归遍历之前进行,是因为在递归遍历中可能会遇到循环引用的情况,如果不先将新对象或数组保存在 copyObjs
中,那么在处理循环引用时就无法判断当前对象是否已经被拷贝过了,从而可能会陷入无限递归的情况。
举个例子,假设有一个对象 A,其中包含一个属性是指向自己的引用,即 A 的某个属性的值是 A 自身。如果不先将 A 拷贝后的副本保存在 copyObjs
中,那么在递归遍历 A 时会陷入无限递归的情况,因为递归到指向自身的属性时会一直循环下去。
而如果先将 A 拷贝后的副本保存在 copyObjs
中,那么在递归遍历 A 时就可以判断当前对象是否已经被拷贝过了,从而避免陷入无限递归的情况。因此,这一步需要放在递归遍历之前进行。
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
// 初始化返回结果
let result = Array.isArray(target) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 保证key不是原型的属性
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
function deepClone(target) {
// 此数组存放已经递归到的对象
let copyObjs = []
function _deepClone(target) {
// 不是对象直接返回
if (typeof target !== 'object') return target
// 如果目标对象和copyobjs保存的对象相等 那么不对他进行递归(解决相同引用问题)
for (let i = 0; i < copyObjs.length; i++) {
if (target === copyObjs[i].target) return copyObjs[i].copyTarget
}
let obj = {}
// 处理目标对象是数组的情况
if (Array.isArray(target)) obj = []
// 在递归之前保存已经递归到的目标对象(解决循环引用问题)
copyObjs.push({ target, copyTarget: obj })
Object.keys(target).forEach((key) => {
obj[key] = _deepClone(target[key])
})
return obj
}
return _deepClone(target)
}
JSON.sringify 和 JSON.parse 方法拷贝的缺陷
这是 JS
实现深拷贝最简单的方法了,原理就是先将对象转换为字符串,再通过 JSON.parse
重新建立一个对象。 但是这种方法的局限也很多:
- 不能复制
function
、正则、Symbol
- 循环引用(当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用)报错
- 相同的引用会被重复拷贝
let obj = { asd: 'asd' }
let obj2 = { name: 'aaaaa' }
obj.ttt1 = obj2
obj.ttt2 = obj2
let cp = JSON.parse(JSON.stringify(obj))
obj.ttt1.name = 'change'
cp.ttt1.name = 'change'
console.log(obj, cp)
对于上面的代码,原对象改变 ttt1.name
也会改变 ttt2.name
,因为他们指向相同的对象。但是,复制的对象中,ttt1
和 ttt2
分别指向了两个对象。拷贝的对象没有保持和原对象一样的结构。因此,JSON
实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复拷贝
如何用 class 实现继承
利用es6
的extends
实现继承没难度,可参考《JS
高级程序设计》es5
实现继承的方式
如何理解 JS 原型(隐式原型和显式原型)和原型链
原型和实例的关系:每个构造函数都有一个原型对象,原型有 一个属性指回构造函数,而实例有一个内部指针指向原型
《
JS
高级程序设计》P238在
JavaScript
中是使用构造函数来新建一个对象的(或者使用 class 类实例化),每一个构造函数的内部都有一个prototype
属性,这个就是显式原型,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个叫做__proto__
的属性,这个属性指向构造函数的prototype
属性对应的值,这个就是隐式原型__proto__

ES5
中新增了一个 Object.getPrototypeOf()
方法,可以通过这个方法来获取对象的原型
原型链查找
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。所以这就是新建的对象为什么能够使用 toString()
等方法的原因
原型链的终点是null
,因为 Object.prototype.__proto__
指向 null
手写一个简易的 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++) {
fn(this[i])
}
}
on(type, fn) {
this.each((elem) => {
elem.addEventListener(type, fn, false)
})
}
}
const $ = new Jquery('p')
console.log($.get(1))
$.on('click', () => {
alert('123')
})
Jquery.prototype.addClass = function (index, className) {
this.get(index).classList.add(className)
}
$.addClass(2, 'wocainima')
// -----------------------------
class JqueryPlus extends Jquery {
constructor(selector) {
super(selector)
}
changeStyle(index, key, value) {
this.get(index).style[key] = value
}
}
const _ = new JqueryPlus('p')
_.changeStyle(0, 'fontSize', '20px')
_.changeStyle(0, 'color', 'red')
简述一下 new 的过程
1)在内存中创建一个新对象
2)将新对象与构造函数通过原型链连接起来
3)将构造函数中的this 绑定到新对象上
4)执行构造函数内部的代码
5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
function Fun(a) {
this.a = a
return { [a]: 1 }
}
const obj = new Fun(2)
console.log(obj) // { '2': 1 }而不是 { a: 2 }
new
方法
实现一个Function.prototype.myNew = function () {
// 1)在内存中创建一个新对象
// 2)将新对象与构造函数**通过原型链**连接起来
const that = Object.create(this.prototype)
// 3)将构造函数中的**this 绑定**到新对象上
// 4)执行构造函数内部的代码
const result = this.apply(that, arguments)
// 5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
return result instanceof Object ? result : that
}
什么是作用域?什么是自由变量?
词法作用域和动态作用域
- 词法(静态)作用域: 在代码书写的时候完成划分,作用域链沿着它定义的位置往外延伸
- 动态作用域: 在代码运行时完成划分,作用域链沿着它的调用栈往外延伸
var name = 'fan'
function showName() {
console.log(name)
}
function changeName() {
var name = 'hang'
showName()
}
changeName() // 词法: fan 动态:hang
全局作用域
声明在任何函数之外的顶层作用域的变量就是全局变量,这样的变量拥有全局作用域
所有未定义直接赋值的变量拥有全局作用域
所有 window 对象的属性拥有全局作用域
全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突
函数作用域
在函数内部定义的变量称为局部变量,拥有函数作用域
只有函数被调用的时候才会形成函数作用域
内层作用域可以访问外层作用域,反之不行
块级作用域
使用 ES6 中新增的 let 和 const 指令可以声明块级作用域
块作用域内的变量只要出了自己被定义的那个代码块,那么就无法访问了。
在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部
自由变量
一个变量在当前作用域没有定义但被使用了
自由变量的查找规则:自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方,因为 js 是词法作用域
如何理解闭包
作用域应用的特殊情况,有两种情况:
- 函数作为参数被传递
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn)
- 函数作为返回值被返回
function create() {
let a = 100
return function () {
console.log(a)
}
}
let fn = create()
let a = 200
fn()
闭包代码输出问题
常见的循环体输出
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
console.log(i) //5 5 5 5 5
如何让它按 1,2,3,4,5 输出?
- setTimeout 从第三个入参位置开始往后,是可以传入无数个参数的。这些参数会作为回调函数的附加参数存在。
for (var i = 0; i < 5; i++) {
setTimeout(
function (j) {
console.log(j)
},
1000,
i
)
}
- 在 setTimeout 外面再套一层函数,利用这个外部函数的入参来缓存每一个循环中的 i 值:
var output = function (i) {
setTimeout(function () {
console.log(i)
}, 1000)
}
for (var i = 0; i < 5; i++) {
// 这里的 i 被赋值给了 output 作用域内的变量 i
output(i)
}
- 在 setTimeout 外面再套一层函数,只不过这个函数是一个立即执行函数。利用立即执行函数的入参来缓存每一个循环中的 i 值:
for (var i = 0; i < 5; i++) {
// 这里的 i 被赋值给了立即执行函数作用域内的变量 j
;(function (j) {
setTimeout(function () {
console.log(j)
}, 1000)
})(i)
}
讲一下闭包的使用场景
待扩展
模拟私有变量的实现
创建 10 个 a,点击弹出对应的序号
<div id="root"></div>
const root = document.getElementById('root')
for (let i = 1; i <= 10; i++) {
const a = document.createElement('a')
a.innerText = i
a.style.display = 'block'
a.onclick = function (e) {
e.preventDefault()
alert(i)
}
root.appendChild(a)
}
手写 bind 函数
提示
bind()
方法创建一个新的函数(这个包装了目标函数),在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用
bind
的用法
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind({ x: 100 }, 10, 20, 30)
const res = fn2()
console.log(res)
- 手写
bind
// 剩余参数是ES6的新特性,一般说来,不支持bind的情况一般也不支持剩余参数,所以,不推荐这种写法
// 不能使用箭头函数 箭头函数不支持arguments
Function.prototype.bind1 = function () {
// 将参数拆解为数组
// call支持最低版本chorme-1
const args = Array.prototype.slice.call(arguments)
// 获取this,数组第一项
const _this = args.shift()
// 返回一个函数
return () => {
// apply支持最低版本chorme-1
// bind支持最低版本chorme-7
this.apply(_this, args)
}
}
// ----------------------------------------
const obj = {
a: 2,
b() {
console.log(this.a)
},
}
const _obj = {
a: 3,
b() {
console.log(this.a)
},
}
obj.b.bind1(_obj)()
this 指向问题
- 作为普通函数使用,指向
window
,严格模式下指向underfind
call
apply
bind
call apply bind 三者的用法和区别 ,指定 this(非箭头函数)- 作为对象方法(非箭头函数)被调用,指向当前对象
- 箭头函数的
this
永远取它上层作用域的this
- 延时器、定时器调用非箭头函数,指向
window
- 全局作用域下指向
window
- 事件处理函数(非箭头函数)的上下文是绑定事件的
DOM
元素
手写 promise 加载图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>promise加载图片</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
const URL = 'https://zfhblog.top/titlelogo.png'
const root = document.getElementById('root')
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
// 加载完
img.onload = () => {
resolve(img)
}
// 加载失败
img.onerror = () => {
const err = new Error(`图片加载失败了,图片地址为${url}`)
reject(err)
}
img.src = url
})
}
loadImg(URL)
.then((img) => {
root.appendChild(img)
console.log('我第二个执行then')
})
.catch((err) => {
console.log(err)
})
console.log('我第一个执行,同步代码')
setTimeout(() => {
console.log('我第三个执行setTimeout')
}, 0)
</script>
</body>
</html>
setTimeout 结果输出题
console.log(1)
setTimeout(function () {
console.log(2)
}, 1000)
console.log(3)
setTimeout(function () {
console.log(4)
}, 0)
console.log(5)
输出结果
: 1 , 3 , 5 , 4 , 2
描述事件循环机制

- 执行一个
宏任务
(主代码块也属于宏任务)(执行栈中没有就从宏任务队列
中获取) - 执行过程中如果遇到
微任务
,就将它添加到微任务
的任务队列中 宏任务
执行完毕后,立即执行当前微任务队列
中的所有微任务
(依次执行)- 尝试DOM渲染
- 开启下一轮事件循环
什么是宏任务什么是微任务,两者区别
- 宏任务:setTimeout, setinterval , Ajax , DOM 事件(浏览器规定的)
- 微任务:Promise(then),async/await(ES6 语法规定的)
- 微任务的执行时机比宏任务早
Promise 的三种状态,如何变化
略
手写 Promise
- 初始化 & 异步调用
- 实例方法
then
catch
链式调用 - 静态方法:
resolve
reject
all
race
any
allSettled
EasyPromise
class EasyPromise {
// promise 的 状态 pending fulfilled rejected
PromiseState = 'pending'
// promise 的 结果
PromiseResult = null
// pending 状态下保存的回调函数任务栈
callBacks = []
constructor(actuator) {
// reslove处理函数
const reslove = (result) => {
if (this.PromiseState !== 'pending') return
this.PromiseResult = result
this.PromiseState = 'fulfilled'
this.callBacks.forEach((fn) => {
fn.onResloved()
})
}
// reject处理函数
const reject = (error) => {
if (this.PromiseState !== 'pending') return
this.PromiseResult = error
this.PromiseState = 'rejected'
this.callBacks.forEach((fn) => {
fn.onRejected()
})
}
try {
actuator(reslove, reject)
} catch (err) {
reject(err)
}
}
then(resloveCallback, rejectCallBack) {
resloveCallback =
typeof resloveCallback === 'function' ? resloveCallback : (v) => v
rejectCallBack =
typeof rejectCallBack === 'function'
? rejectCallBack
: (e) => {
throw e
}
return new EasyPromise((reslove, reject) => {
// 判断返回值的类型
const judgeReturnValueType = (callBack) => {
try {
const result = callBack(this.PromiseResult)
if (result instanceof EasyPromise) {
// 由返回的Promise的状态决定
result.then(
(v) => {
reslove(v)
},
(e) => {
reject(e)
}
)
} else {
reslove(result)
}
} catch (err) {
reject(err)
}
}
switch (this.PromiseState) {
case 'fulfilled':
// 一个微任务
queueMicrotask(() => {
judgeReturnValueType(resloveCallback)
})
break
case 'rejected':
queueMicrotask(() => {
judgeReturnValueType(rejectCallBack)
})
break
default:
this.callBacks.push({
onResloved() {
judgeReturnValueType(resloveCallback)
},
onRejected() {
judgeReturnValueType(rejectCallBack)
},
})
}
})
}
catch(rejectCallBack) {
return this.then(null, rejectCallBack)
}
static all(promiseList = []) {
return new EasyPromise((reslove, reject) => {
const result = [] // 存储PromiseList结果的数组
const length = promiseList.length
let resloveCount = 0 // 结果为成功的个数
promiseList.forEach((p) => {
p.then((data) => {
result.push(data)
resloveCount++
if (resloveCount === length) reslove(result)
}).catch((err) => {
reject(err)
})
})
})
}
static any(promiseList = []) {
return new EasyPromise((reslove, reject) => {
let rejectCount = 0 // 结果为失败的个数
const length = promiseList.length
promiseList.forEach((p) => {
p.then((data) => {
reslove(data)
}).catch(() => {
rejectCount++
if (rejectCount === length) {
reject('All promises were rejected')
}
})
})
})
}
static race(promiseList = []) {
return new EasyPromise((reslove, reject) => {
promiseList.forEach((p) => {
p.then((data) => {
reslove(data)
}).catch((err) => {
reject(err)
})
})
})
}
static allSettled(promiseList = []) {
return new EasyPromise((reslove, reject) => {
let result = []
promiseList.forEach((p) => {
p.then((data) => {
result.push({ status: 'fulfilled', value: data })
}).catch((err) => {
result.push({ status: 'rejected', reason: err })
})
})
reslove(result)
})
}
static reslove(value) {
return new EasyPromise((reslove, reject) => {
reslove(value)
})
}
static reject(reason) {
return new EasyPromise((reslove, reject) => {
reject(reason)
})
}
}
property 和 attribute 的区别
property
:修改对象属性,不会提现到html
结构中attribute
:修改html
属性,会改变html
结构- 两者都有可能引起
DOM
的重新渲染
如何优化 DOM 操作的性能
注意
涉及前端性能优化,此处答案过于简单,后期扩展
- DOM 查询做缓存
// 不缓存DOM查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环 都会计算length 频繁进行DOM操作
}
// 缓存DOM查询结果
const pList = document.getElementsByTagName('p')
const length = pList.length
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
// 缓存length 只进行一次DOM查询
}
- 将频繁操作改为一次操作
const list = document.querySelector('.list')
// 文档切片
const frag = document.createDocumentFragment()
for (let i = 0; i < 10000; i++) {
const title = document.createElement('h1')
title.innerText = `list item ${i}`
frag.appendChild(title)
}
list.appendChild(frag)
编写一个通用的事件监听函数
const bindEvent = (elem, type, selector, fn) => {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, (event) => {
const target = event.target
if (selector) {
// 代理
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通
fn.call(target, event)
}
})
}
无限下拉的图片列表,如何监听每个图片的点击
- 事件代理
- 用
event.target
获取触发元素 - 用``matches`来判断是否是触发元素
柯里化
实现 add(1)(2)(3)Object.is()与比较操作符的区别
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊情况,比如-0 和+0 不再相等,两个 NaN 是相等的
箭头函数与普通函数的区别
- 箭头函数没有自己的 this
- call、apply、bind 不会改变箭头函数的 this 指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有 arguments
- 箭头函数没有 Prototype
简述 new 操作符的执行过程
1)在内存中创建一个新对象
2)将新对象与构造函数通过原型链连接起来
3)将构造函数中的this 绑定到新对象上
4)执行构造函数内部的代码
5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
function Fun(a) {
this.a = a
return { [a]: 1 }
}
const obj = new Fun(2)
console.log(obj) // { '2': 1 }而不是 { a: 2 }