箭头函数和普通函数有什么区别
- 箭头函数没有自己的 this
- 箭头函数的 this 是继承于自己作用域的上一层 this
- 箭头函数不能作为构造函数使用
JSON.stringify 有什么缺点
- 如果 obj 里有 正则表达式的缩写、Error 对象,则序列化的结果将只得到
空对象
- 如果 obj 里有 函数,undefined,则序列化的结果将只得到
undefined
- 如果 obj 里有 NaN、Infinity 和 -Infinity,则序列化的结果将只得到
null
- 使用
JSON.parse(JSON.stringify(obj))
深拷贝后,会丢弃对象的 constructor
for…in 和 for…of 的区别
for...of
遍历获取的是对象的键值;for...in
遍历获取的是对象的键名for...of
只遍历当前对象不会遍历原型链 ;for...in
遍历对象的整个原型链for...in
循环主要是为了遍历对象而生,不适用遍历数组;
// 定义一个对象
var obj = {"name": "Clark", "surname": "Kent", "age": "36"};
// 使用 for of 循环遍历对象中的所有属性
for(var value in obj) {
document.write(value + ", ");
}
for....of
循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象
// 定义一个数组
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
// 使用 for of 循环遍历数组中的每个元素
for (var value of arr) {
document.write(value + ", ");
}
form 的 enctype 编码方式
application/x-www-form-urlencoded
:表单数据编码为键值对并用 & 进行分割(默认编码)multipart/form-data
:表单数据编码为一条信息,每个表单控件对应信息的一部分text/plain
:表单数据以纯文本形式进行编码
application/x-www-form-urlencoded
-
当 action 为 GET 时,表单数据编码为(account=value1&password=value2),然后将该字符串拼接 url,用 ?进行分割(http://localhost:3000/login?account=value1&password=value2)
-
当 action 为 POST 时,浏览器会将 form 的数据封装到 http body 中,并发送到 server
multipart/form-data
:
- 在表单控件中,如果存在
type=file
时,需要设置 enctype 为multipart/form-data
,浏览器会把表单以控件为单位分割
application/x-www-form-urlencoded
与 multipart/form-data
对比:
application/x-www-form-urlencoded
:向服务器发送大量的文本、包含非ASCII字符的文本或二进制数据时这种编码方式效率很低- 在文件上载时,所使用的编码类型应当是“multipart/form-data”,它既可以发送文本数据,也支持二进制数据上传
执行上下文
什么是执行上下文
指当前执行环境中的变量、函数声明、作用域链、this
执行上下文生命周期
1、创建阶段
生成变量对象、建立作用域链、确定 this 的指向
2、执行阶段
变量赋值、函数引用、执行其他代码
执行上下文特点
1)单线程,只在主线程上运行
2)同步执行,从上向下按顺序执行
3)全局上下文只有一个,也就是 window对象
4)函数每调用一次就会产生一个新的执行上下文环境
作用域
什么是作用域
可访问变量的集合
好处
隔离变量,不同作用域下同名变量不会有冲突
类型
1、全局作用域
2、函数作用域:是指声明在函数内部的变量,函数的作用域在函数定义的时候就决定了
3、块级作用域:
-
块作用域由
{ }
包括,if和for语句里面的{ }
也属于块作用域 -
在块级作用域中,可通过let和const声明变量,该变量在指定块的作用域外无法被访问
var、let、const 区别
1)var定义的变量,没有块的概念,可以跨块访问, 可以变量提升
2)let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明
3)const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明
作用域链
当程序在查找变量的时候,首先会先从当前上下文的变量对象(作用域)中查找,如果没有找到,就会从父级的执行上下文的变量对象中查找,如果还没找到,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
this 绑定方式
1、默认绑定(非严格模式下 this 指向全局对象,严格模式下函数内的 this 指向 undefined)
2、隐式绑定(当函数引用有上下文对象时,如obj.foo()的调用方式,foo内的 this 指向 obj
)
3、显示绑定(通过 call 或者 apply 方式直接指定 this 的绑定对象,如 foo.call(obj)
)
4、new 构造函数绑定,this 指向新生成的对象
5、箭头函数, this 指向的时定义函数时,外部环境中的 this,箭头函数的 this 在定义时就决定了,不能修改。
call apply bind 区别
1、三者都可以显示般的函数的 this 指向
2、三者第一个参数都是 this 要指向的对象,若该参数为 undefined 或 null, this 则默认指向全局 window
3、传参不同:apply 是数组、call 是参数列表、而 bind 可以分多次传入,实现参数的合并
4、call apply 是立即执行,bind 是返回绑定 this 之后的函数,如果这个新的函数作为构造函数被调用,那么 this 不再指向传入给 bind 的第一个参数,而是指向新生成的对象
闭包
什么是闭包
在函数中能够引用外部其他作用域(在该函数中)的变量
常见情况
1)函数作为返回值
2)函数作为参数传递
作用
1)可以让局部变量的值始终保存在内存中
2)对内部变量进行保护,使外部访问不到
缺点
1)不合理的使用闭包,会造成内存泄露(该内存空间使用完毕之后未被回收)
2)闭包中引用的变量直到闭包被销毁时才会被垃圾回收
原型/原型链
原型作用
为其他对象提供共享属性的对象,函数的实例可以共享原型上的属性和方法
原型链作用
它的作用就是当你在访问一个对象上属性的时候,如果该对象内部不存在这个属性,那么就会去它__proto__
属性所指向的对象(原型对象)上查找。如果原型对象依旧不存在这个属性,那么就会去其原型的__proto__
属性所指向的原型对象上去查找。以此类推,直到找到nul
,而这个查找的线路,也就构成了我们常说的原型链
原型链和作用域链的区别:原型链是查找对象上的属性,作用域链是查找当前上下文中的变量
proto、prototype、constructor 属性
1)js 中对象分为两种,普通对象和函数对象
2)proto 和 constructor 属性是对象独有的。
3)而 prototype属性 是函数所独有的。它作用是包含可以给特定类型的所有实例提供共享的属性和方法。
4)在js中,万事万物皆对象,所以函数也有 proto 和 prototype 属性
5)proto属性是来连接对象直到null的一条链即为原型链
6)constructor属性含义是指向该对象的构造函数,最终的构造函数都指向Funtion
https://www.jianshu.com/p/173f651ccf45
instanceof
instanceof
的基本用法,它可以判断一个对象的原型链上是否包含该构造函数的原型,经常用来判断对象是否为该构造函数的实例
console.log(Object instanceof Object); //true
console.log(Function instanceof Function); //true
console.log(Function instanceof Object); //true
console.log(function() {} instanceof Function); //true
instanceof 与 typeof 区别
1、typeof
一般被用于来判断一个变量的类型
特殊情况:typeof null === 'object'
2、instanceof
判断一个对象的原型链上是否包含该构造函数的原型
new 关键字
new 对象,到底发生什么?
1)创建一个对象,该对象的原型指向该构造函数
2)构造函数的 this 会指向该对象
深拷贝
1) JSON.parse(JSON.stringify())
缺点: 无法拷贝 函数、正则、时间格式、原型上的属性和方法等
2)递归实现深拷贝
/**
* 数据拷贝
* @param {Array | Object} obj 待拷贝的数据
* @returns
*/
const copyData = (obj = {}) => {
let newObj = null
if (typeof (obj) === 'object' && obj !== null) {
newObj = obj instanceof Array ? [] : {}
// 进入下一层进行递归
for (let i in obj) newObj[i] = copyData(obj[i])
} else {
newObj = obj
}
return newObj
}
事件轮询机制
JS 语言最大的特点就是 单线程,即同一个时间只能做一件事。所有任务都需要排队。前一个任务结束,才能执行后一个任务。
这由此就会导致:如果正在执行的任务出现执行时间过长,则后一个任务得一直等着,导致后续任务无法执行。
为了解决这个问题:JS 将所有任务分成两种:**** 和 微任务
-
宏任务:在主线程上排队执行的任务
-
微任务:不进入主线程,而是进入“微任务列表”
什么是宏任务:
- script全部代码(注意同步代码也属于宏任务)、setTimeout、setInterval、setImmediate等
什么是微任务:
- Promise、MutationObserver
事件轮询机制执行过程:
1)代码执行过程中,宏任务和微任务放在不同的任务队列中
2)当某个宏任务执行完后,会查看微任务队列是否有任务。如果有,执行微任务队列中的所有微任务(注意这里是执行所有的微任务)
3)微任务执行完成后,会读取宏任务队列中排在最前的第一个宏任务(注意宏任务是一个个取),执行该宏任务,如果执行过程中,遇到微任务,依次加入微任务队列
4)宏任务执行完成后,再次读取微任务队列里的任务,依次类推。
Promise.resolve()
.then(function() {
console.log("promise0");
}).then(function() {
console.log("promise5");
});
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise2");
});
Promise.resolve().then(function() {
console.log("promise4");
});
}, 0);
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function() {
console.log("promise3");
});
}, 0);
Promise.resolve().then(function() {
console.log("promise1");
});
console.log("start");
// start promise0 promise5 promise1 timer1 promise2 promise4 timer2 promise3
事件轮询机制 与 浏览器更新渲染时机
1) 浏览器更新渲染会在event loop中的 宏任务 和 微任务 完成后进行,即宏任务 → 微任务 → 渲染更新
(先宏任务 再微任务,然后再渲染更新)
2)宏任务队列中,如果有大量任务等待执行时,将dom的变动作为微任务,能更快的将变化呈现给用户
,这样就可以在这一次的事件轮询中更新dom
定时器不准的原因
setTimeout/setInterval是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行
考虑极端情况,假如定时器里面的代码需要进行大量的计算,或者是DOM操作,代码执行时间超过定时器的时间,会出现定时器不准的情况
setTimeout/setInterval 动画卡顿
不同设备的屏幕刷新频率可能不同, setTimeout/setInterval只能设置固定的时间间隔,这个时间和屏幕刷新间隔可能不同
setTimeout/setInterval通过设置一个间隔时间,来不断改变图像实现动画效果,在不同设备上可能会出现卡顿、抖动等现象
requestAnimationFrame
requestAnimationFrame
是浏览器专门为动画提供的API
requestAnimationFrame刷新频率与显示器的刷新频率保持一致,使用该api可以避免使用setTimeout/setInterval造成动画卡顿的情况
requestAnimationFrame:告诉浏览器在下次重绘之前执行传入的回调函数(通常是操纵dom,更新动画的函数)
setTimeout、setInterval、requestAnimationFrame 三者的区别
1)引擎层面
- setTimeout,setInterval 属于
JS引擎
,存在事件轮询 - requestAnimationFrame 属于
GUI引擎
JS引擎与GUI引擎
是互斥的,也就是说 GUI引擎在渲染时会阻塞JS引擎的计算
这样设计的原因,如果在GUI渲染的时候,JS同时又改变了dom,那么就会造成页面渲染不同步
2)性能层面
- 当页面被隐藏或最小化时,setTimeout,setInterval 仍会在后台执行动画任务
- 页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停,requestAnimationFrame也会停止