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 位有效数字,这样在进制之间的转换中精度已经损失
同理,0.2 的二进制数为
(1.100110011001100110011001100110011001100110011001101 * 2) ^ -3
这样在进制之间的转换中精度已经损失。运算的时候如下
- 那么如何让其相等呢? 使用第三方库decimal.js:
const Decimal = require('decimal.js');
const x = new Decimal(0.2)
console.log(x.plus(0.1)) //0.3
为什么不是toFixed?
toFixed在一些情况下,会出现和预期不符的结果:
const r1=1.1+0.35 const r2=1.1+0.45 console.log(r1.toFixed(1)) //1.4 四舍五入希望是1.5 console.log(r2.toFixed(1)) //1.6 // why? 1.45.toPrecision(20) // 计算机实际存储的值偏小,toPrecision返回一个以指定精度表示该数字的字符串'1.4499999999999999556'
数据类型检测的方式有哪些
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 原始类型有六种,分别为:
BooleanStringNumberUndefinedNullSymbol(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 callapplybindcall 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
- 初始化 & 异步调用
- 实例方法
thencatch链式调用 - 静态方法:
resolverejectallraceanyallSettled
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 }

