call,apply,bind这三者的区别,及内部实现原理, 点这里
promise函数的内部实现原理, 点这里
闭包就是能够读取其他函数内部变量的函数。形式上,就是一个函数返回一个内部函数到函数外,内部函数引用外部函数的局部变量。本质上,闭包是将函数内部和函数外部连接起来的桥梁。
JavaScript中每一个对象都有一个__proto__和constructor属性,每一个函数都有一个prototype属性,因函数也是对象,所以函数也拥有__proto__和constructor属性。
__proto__指向的是它们的原型对象,也可以理解为父对象。如果访问本身一个不存在的属性,那么没有获取之后会去它的原型对象去获取,而原型对象本身也是一个普通对象,如果在它的原型对象中同样没有获取到,那么就会往原型对象的原型对象去获取,直到顶层对象null(原型链终点,一个没有任何属性的对象),返回undefined。这就形成了一条原型链。
prototype属性是函数独有的,是从一个函数指向一个对象,称之为函数的原型对象。原型对象内包含特定类型所有实例共享的属性和方法,作用为被该函数实例化出来的对象找到共用的属性和方法。
constructor是从一个对象指向一个函数,称之为该对象的构造函数。每个对象都有对应的构造函数,因为对象的建立前提是需要有constructor。
js 异步并发控制一个很重要的场景就是大文件的分片上传,加上 http1.1 能够同时发送6个请求,将大文件分成多个片段进行并发请求,能够减少上传时间,并且能够进行断点续传与暂停上传等功能。
以下是异步并发控制大致逻辑:
function sendRequest(arr, max = 6, callback) {
let i = 0 // 数组下标
let fetchArr = [] // 正在执行的请求
let toFetch = () => { // 如果异步任务都已开始执行,剩最后一组,则结束并发控制
if (i === arr.length) { return Promise.resolve()
} // 执行异步任务
let it = fetch(arr[i++]) // 添加异步事件的完成处理
it.then(() => {
fetchArr.splice(fetchArr.indexOf(it), 1)
})
fetchArr.push(it)
let p = Promise.resolve() // 如果并发数达到最大数,则等其中一个异步任务完成再添加
if (fetchArr.length >= max) {
p = Promise.race(fetchArr)
} // 执行递归
return p.then(() => toFetch())
}
toFetch().then(() =>
// 最后一组全部执行完再执行回调函数
Promise.all(fetchArr).then(() => {
callback()
})
)
}节流:
节流是在规定的时间内只执行一次,稀释函数执行频率。比如规定时间2s内执行了一次函数,那么在这2s内再次触发将不会执行。
function throttle(time, fn) {
let isRun = false
return function () { if (isRun) return
isRun = true
let arg = [...arguments]
setTimeout(() => {
fn.apply(null, arg)
isRun = false
}, time * 1000)
}
}防抖:
防抖是在等待的时间内不断触发函数,但函数真正执行的将是最后触发的那次。比如规定时间为2s,如果第二次与第一次的触发的时间间隔小于2s,那么第一次将会被清除,留第二次触发的函数继续等待,如果2s内没有第三次触发,将执行第二次触发的函数,如果2s内又触发了第三次,那么第二次触发的函数也将被清除,留第三次触发的函数继续等待。
function debounce(time, fn) {
let timer = null
return function () {
let arg = [...arguments] if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(null, arg)
clearTimeout(timer)
timer = null
}, time * 1000)
}
}斐波那契数列:1、1、2、3、5、8、13、21、……
// 递归function fibonacci(num) { if (num === 1 || num === 2) { return 1
} return fibonacci(num - 2) + fibonacci(num - 1)
}// 循环function fibonacci1(n) { var n1 = 1, n2 = 1, sum; for (let i = 2; i < n; i++) {
sum = n1 + n2
n1 = n2
n2 = sum
} return sum
}快速排序:
function quickSortFn(_arr) {
let arr = [..._arr] if (arr.length <= 1) { return arr
}
let left = []
let right = []
let item = arr.pop() for (let i = 0, len = arr.length; i < len; i++) {
let val = arr[i] if (val >= item) {
right.push(val)
} else {
left.push(val)
}
} return [...quickSortFn(left), item, ...quickSortFn(right)]
}冒泡排序:
function bubbleSort(_arr) {
let arr = [..._arr]
let len = arr.length for (let i = 0; i < len - 1; i++) { for (let k = i + 1; k < len; k++) { if (arr[i] > arr[k]) {
[arr[i], arr[k]] = [arr[k], arr[i]]
}
}
} return arr
}// 第一种let a = [1,2,[3,4],[5,[6,[7,8]],9]]
a.join(',').split(',')// 第二种function unid1(arr) { for (let item of arr) { if (Object.prototype.toString.call(item).slice(8, -1) === 'Array') {
unid1(item);
} else {
result.push(item);
}
} return result;
}function LazyMan(name) { this.task = []
let self = this
let fn = (name => { return () => {
console.log(name)
self.next()
}
})(name)
self.task.push(fn)
setTimeout(() => {
console.log(222)
self.next()
})
}
LazyMan.prototype = {
constructor: LazyMan,
next() {
let fn = this.task.shift()
fn && fn()
},
eat(val) {
let self = this
self.task.push((val => { return () => {
console.log(val)
self.next()
}
})(val)) return this
},
sleep(num) {
let self = this
self.task.push((num => { return () => {
setTimeout(() => {
console.log(num)
self.next()
}, +num * 1000)
}
})(num)) return this
}
}function lazyMan(name) { return new LazyMan(name)
}
lazyMan('zz').eat('lunch').sleep('3').eat('dinner')深拷贝与浅拷贝的区别本质是被复制出来的值的内存地址是否有改变,内存地址没变就是浅拷贝,有变就是深拷贝。这里涉及到了JavaScript的引用数据类型,引用数据类型的复制,复制的不是对象本身,而是一个指向该对象的指针,当这个对象本身的值改变,那么所有引用这个对象的变量都会改变。
浅拷贝:
Object.assign()
深拷贝:
JSON.parse(JSON.stringify(object)):
这个能够拷贝除Function、RegExp与undefined等类型之外的值,如果遇到这种类型,将会被自动忽略。
循环递归拷贝:
function getType(val) { return Object.prototype.toString.call(val).slice(8, -1)
}function deepClone(obj) { if (obj && typeof obj === 'object') {
let returnObj = getType(obj) === 'Array' ? [] : {}
let item = '' for (let key in obj) {
item = obj[key] if (key === "__proto__") { continue;
} if (getType(item) === 'Array' || getType(item) === 'Object') {
returnObj[key] = deepClone(item)
} else {
returnObj[key] = item
}
} return returnObj
}
}JavaScript语言的核心特点就是单线程,单线程的原因主要是对DOM的操作,多线程操作DOM会引起冲突。为了利用多核CPU的计算能力,HTML5提出了web worker标准,允许JavaScript创建多线程,且创建线程完全受主线程控制,且不得操作DOM。
js的异步是通过回调函数实现的,即任务队列。虽然js是单线程的,但浏览器的多线程的,则js的执行遇到异步任务都会调用浏览器的多线程去执行,当异步任务有了结果,则会将异步任务的回调函数放入异步任务队列。
任务队列分为两种:宏任务队列与微任务队列。
当js从上往下执行时,如遇到异步任务,浏览器则用其他线程去执行,当异步任务有了结果,则将回调函数放到任务队列中,当主执行栈执行完后,会去查询微任务队列,如果有则执行,微任务队列执行完后,则将宏任务队列放入主执行栈重新开始下一轮循环。
不同的js异步API的回调函数放入不同的任务队列。
宏任务(macrotask)队列API:
微任务(microtask)队列API:
注意的一点:微任务队列中的微任务回调函数是放入当前微任务队列中,而不是下轮循环队列。
该点的解释则是表明JavaScript程序内部的执行机制。
执行上下文,简而言之,就是当前JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript任何代码都是在执行上下文中运行。
三种类型:
生命周期:
管理执行上下文:
所有的执行上下文采用的是栈结构来管理,遵循先进后出。全局JavaScript代码在浏览器执行时,实现创建一个全局执行上下文,压入执行栈的底端,每创建一个函数执行上下文,则把它压入执行栈的顶端,等待函数执行完,该函数的执行上下文出栈等待回收。
JavaScript解析引擎总是访问执行栈的顶端,当浏览器关闭,则全局执行上下文出栈。
async/await的作用为阻塞异步执行任务,等待异步任务执行完返回,再执行下面任务,异步任务返回的是一个Promise对象。
实现原理为generator + yield + promise:generator自动执行且返回一个promise对象。
let test = function () { // ret 为一个Promise对象,因为ES6语法规定 async 函数的返回值必须是一个 promise 对象
let ret = _asyncToGenerator(function* () { for (let i = 0; i < 10; i++) {
let result = yield sleep(1000);
console.log(result);
}
}); return ret;
}();// generator 自执行器function _asyncToGenerator(genFn) { return new Promise((resolve, reject) => {
let gen = genFn(); function step(key, arg) {
let info = {}; try {
info = gen[key](arg);
} catch (error) {
reject(error); return;
} if (info.done) {
resolve(info.value);
} else { return Promise.resolve(info.value).then((v) => { return step('next', v);
}, (error) => { return step('throw', error);
});
}
}
step('next');
});
}跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
而狭义的跨域是指:当浏览器与服务器通信的两个地址的协议、域名、端口,这三者任意一个不同,都会导致跨域问题的产生,这是基于浏览器的同源策略限制。
限制的行为:
解决方案:
正向代理与反向代理的区别:
正向代理与反向代理并没有形式上的区别,只是一个认知的问题。比如a请求b有跨域问题,正向代理与反向代理都可以通过中介c来实现,a -> c -> b -> c -> a这样完成了一次跨域通信,如果a请求c,知道c会去请求b再返回,则是一个正向代理,如果a不知道请求c,c最终去请求了b,那这就是一个反向代理。最终目的地址以IP为准。
优雅降级:一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
渐进增强:一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
优雅降级和渐进增强都关注于同一网站在不同设备里不同浏览器下的表现程度。关键的区别则在于它们各自关注于何处,以及这种关注如何影响工作的流程。
优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站。而将那些被认为“过时”或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段,并把测试对象限定为主流浏览器(如 IE、Mozilla 等)的前一个版本。在这种设计范例下,旧版的浏览器被认为仅能提供“简陋却无妨 (poor, but passable)” 的浏览体验。你可以做一些小的调整来适应某个特定的浏览器。但由于它们并非我们所关注的焦点,因此除了修复较大的错误之外,其它的差异将被直接忽略。
渐进增强观点则认为应关注于内容本身。请注意其中的差别:我甚至连“浏览器”三个字都没提。内容是我们建立网站的诱因。有的网站展示它,有的则收集它,有的寻求,有的操作,还有的网站甚至会包含以上的种种,但相同点是它们全都涉及到内容。这使得渐进增强成为一种更为合理的设计范例。这也是它立即被 Yahoo! 所采纳并用以构建其“ 分级式浏览器支持 (Graded Browser Support)”策略的原因所在。
这两者之间的关系:重绘不一定重排,而重排一定重绘。
重排:当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
重绘:在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
引起的原因:
重排:
除此之外,当我们调用getComputedStyle方法,或者IE里的currentStyle时,也会触发回流,原理是一样的,都为求一个“即时性”和“准确性”。
重绘:
优化:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69959503/viewspace-2722523/ ,如需转载,请注明出处,否则将追究法律责任。