1. JavaScript 执行机制 event loop
原理:
- js是单线程执行的
- 先主任务,在执行回调任务,promise为同步任务,then是异步任务
- 然后是执行微任务(promise,requestAnimationFrame)
- 再执行宏任务(setTimeout,setInterval, UI render)
例子:
setTimeout(() => {
console.log('1');
setInterval(()=>{
console.log('6')
})
Promise.resolve().then( () =>{
console.log('2');
})
}, 0);
new Promise((resolve, reject) => { // 同步执行
console.log('3');
resolve()
})
.then(res =>{
console.log('4');
})
console.log('5');
// 3,5,4,1,2,6
闭包
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期
对象
原型链和继承
this
this
关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
根据不同的使用场合,this
有不同的值,主要分为下面几种情况:
- 默认绑定
- 严格模式下,不能将全局对象用于默认绑定,this会绑定到
undefined
,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
- 严格模式下,不能将全局对象用于默认绑定,this会绑定到
- 隐式绑定
- 函数作为某个对象的方法调用时,this 就指这个上级对象
- new绑定
- 通过构建函数
new
关键字生成一个实例对象,此时this
指向这个实例对象 new
过程遇到return
一个对象,此时this
指向为返回的对象
- 通过构建函数
- 显示绑定
- apply、call、bind
箭头函数
绑定事件监听和在原型上添加方法时候,此时 this
指向 window
箭头函数不能作为构建函数
优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
call apply和bind的区别
- 三者都可以改变函数的
this
对象指向 - 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
- 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入 bind
是返回绑定this之后的函数,apply
、call
则是立即执行
防抖和节流
防抖
防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
// 防抖
function debounce(fn, delay) {
var timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
// timer = setTimeout(fn,delay); // 和下面代码效果一样,但解决了this指向问题
timer = setTimeout(() => {
fn.apply(context, args)
}, delay);
};
}
window.onscroll = debounce(handlerScroll, 200);
// 方法
function handlerScroll() {
var scrollTop =
document.documentElement.scrollTop;
console.log(scrollTop);
}
应用场景
- 输入框频繁输入内容,搜索或者提交信息。
- 频繁点击按钮,触发某个事件。
- 监听浏览器滚动事件。
- 监听用户缩放浏览器resize事件。
节流
节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
节流函数会按照一定的频率来执行函数 。
// 节流
function throttle(fn, delay) {
var valid = true;
return function () {
if (!valid) {
return false;
}
valid = false;
setTimeout(function () {
fn();
valid = true;
}, delay);
};
}
window.onscroll = throttle(handlerScroll, 200);
// 方法
function handlerScroll() {
var scrollTop =
document.documentElement.scrollTop;
console.log(scrollTop);
}
应用场景
- 时间戳
- 定时器
作用域(作用域链)
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
作用域分成:
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域
任何不在函数中或是大括号中声明的变量
函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域
ES6引入了 let
和 const
关键字,和 var
关键字不同,在大括号中使用 let
和 const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
原型链
__proto__
作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的
每个对象的 __proto__
都是指向它的构造函数的原型对象 prototype
的
person1.__proto__ === Person.prototype
总结
- 一切对象都是继承自
Object
对象,Object
对象直接继承根源对象null
- 一切的函数对象(包括
Object
对象),都是继承自Function
对象 Object
对象直接继承自Function
对象Function
对象的__proto__
会指向自己的原型对象,最终还是继承自Object
对象
Promise & async / await 异步编程
async
就是用来声明一个异步方法,而 await
是用来等待异步方法执行
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
分析过程:
- 执行整段代码,遇到
console.log('script start')
直接打印结果,输出script start
- 遇到定时器了,它是宏任务,先放着不执行
- 遇到
async1()
,执行async1
函数,先打印async1 start
,下面遇到await
怎么办?先执行async2
,打印async2
,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码 - 跳到
new Promise
这里,直接执行,打印promise1
,下面遇到.then()
,它是微任务,放到微任务列表等待执行 - 最后一行直接打印
script end
,现在同步代码执行完了,开始执行微任务,即await
下面的代码,打印async1 end
- 继续执行下一个微任务,即执行
then
的回调,打印promise2
- 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印
settimeout
所以最后的结果是:script start
、async1 start
、async2
、promise1
、script end
、async1 end
、promise2
、settimeout
深copy和浅copy
浅copy
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
Object.assign
Array.prototype.slice()
,Array.prototype.concat()
- 使用拓展运算符实现的复制
// slice(0) 表示不截取,复制
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
// concat
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
// 展开运算符
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
深copy
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep() – lodash
- jQuery.extend() – jquery
- JSON.stringify() — 会忽略
undefined
、symbol
和函数
- 手写循环递归
// 手写循环递归
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
浏览器 DOM 事件流 / 事件委托
DOM 事件流
传播按顺序分为三个阶段:捕获阶段、目标阶段、冒泡阶段
Dom
标准事件流的触发的先后顺序为:先捕获再冒泡
**
e.stopPropagation()** 取消冒泡
事件委托
事件委托
也称为 事件代理
。就是利用 事件冒泡
,把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托就无法实现。
实现原理
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
优点
-
替代循环绑定事件的操作,减少内存消耗,提高性能。比如:
- 在
table
上代理所有td
的click
事件。 - 在
ul
上代理所有li
的click
事件。
- 在
-
简化了
dom
节点更新时,相应事件的更新。比如:- 不用在新添加的
li
上绑定click
事件。 - 当删除某个
li
时,不用移解绑上面的click
事件。
- 不用在新添加的
var、let、const之间的区别
let与const
- let是用于替代var来声明变量(var是ES6之前用来声明变量的关键词)
- const是用来声明常量的
区别:
- var,let声明变量时,变量一旦初始化之后,还可以重新赋值,
- const声明常量,一旦初始化,就不能重新赋值了,否则会报错
- 使用const声明常量,一旦声明,就必须立即初始化
let、const、var的区别
①重复声明 ②变量提升 ③暂时性死区 ④块级作用域 ⑤window对象的属性和方法(全局作用域中)
- var允许重复声明,let、const不允许
- var会提升变量的声明到作用域的顶部,但let和const不会
- 只要作用域内存在let、const,它们所声明的变量或常量就会自动“绑定”这个区域,不再受外部作用域的影响
- 全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会
- var没有块级作用域,let和const有块级作用域
# HTTP 请求过程
⭐️ 常见状态码
- 1xx 信息 接受的请求正在处理
- 2xx 成功 接受的请求正在处理
- 3xx 重定向
- 301 永久性重定向
- 302 临时性重定向
- 303 资源的URI已更新,你是否能临时按新的URI访问
- 304 资源已找到,但未符合条件请求
- 4xx 客户端错误
- 400 Bad Request 语义有误
- 401 Unauthorized 服务器认证失败
- 403 Forbidden 服务器拒绝访问
- 404 Forbidden 地址错误
- 5xx 服务器错误
- 500 服务器遇到意外
- 503 服务器停机维护或者已超载