本周面试题一览:
更多优质文章可戳: https://github.com/YvetteLau/...javascript
闭包是指有权访问另外一个函数做用域中的变量的函数,建立闭包最经常使用的方式就是在一个函数内部建立另外一个函数。html
function foo() { var a = 2; return function fn() { console.log(a); } } let func = foo(); func(); //输出2
闭包使得函数能够继续访问定义时的词法做用域。拜 fn 所赐,在 foo() 执行后,foo 内部做用域不会被销毁。html5
不管经过何种手段将内部函数传递到所在的词法做用域以外,它都会持有对原始定义做用域的引用,不管在何处执行这个函数都会使用闭包。如:java
function foo() { var a = 2; function inner() { console.log(a); } outer(inner); } function outer(fn){ fn(); //闭包 } foo();
function base() { let x = 10; //私有变量 return { getX: function() { return x; } } } let obj = base(); console.log(obj.getX()); //10
var a = []; for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i); } a[6](); // 6
function coolModule() { let name = 'Yvette'; let age = 20; function sayName() { console.log(name); } function sayAge() { console.log(age); } return { sayName, sayAge } } let info = coolModule(); info.sayName(); //'Yvette'
模块模式具备两个必备的条件(来自《你不知道的JavaScript》)git
闭包会致使函数的变量一直保存在内存中,过多的闭包可能会致使内存泄漏es6
在实现 Promise.all 方法以前,咱们首先要知道 Promise.all 的功能和特色,由于在清楚了 Promise.all 功能和特色的状况下,咱们才能进一步去写实现。github
Promise.all(iterable)
返回一个新的 Promise 实例。此实例在 iterable
参数内全部的 promise
都 fulfilled
或者参数中不包含 promise
时,状态变成 fulfilled
;若是参数中 promise
有一个失败rejected
,此实例回调失败,失败缘由的是第一个失败 promise
的返回结果。面试
let p = Promise.all([p1, p2, p3]);
p的状态由 p1,p2,p3决定,分红如下;两种状况:数组
(1)只有p一、p二、p3的状态都变成 fulfilled
,p的状态才会变成 fulfilled
,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。promise
(2)只要p一、p二、p3之中有一个被 rejected
,p的状态就变成 rejected
,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.all 的返回值是一个 promise 实例
Promise.all
会 同步 返回一个已完成状态的 promise
Promise.all
会 异步 返回一个已完成状态的 promise
Promise.all
返回一个 处理中(pending) 状态的 promise
.Promise.all 返回的 promise 的状态
Promise.all
返回的 promise
异步地变为完成。promise
失败,Promise.all
异步地将失败的那个结果给失败状态的回调函数,而无论其它 promise
是否完成Promise.all
返回的 promise
的完成状态的结果都是一个数组仅考虑传入的参数是数组的状况
/** 仅考虑 promises 传入的是数组的状况时 */ Promise.all = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { resolve([]); } else { let result = []; let index = 0; for (let i = 0; i < promises.length; i++ ) { //考虑到 i 多是 thenable 对象也多是普通值 Promise.resolve(promises[i]).then(data => { result[i] = data; if (++index === promises.length) { //全部的 promises 状态都是 fulfilled,promise.all返回的实例才变成 fulfilled 态 resolve(result); } }, err => { reject(err); return; }); } } }); }
可以使用 MDN 上的代码进行测试
考虑 iterable 对象
Promise.all = function (promises) { /** promises 是一个可迭代对象,省略对参数类型的判断 */ return new Promise((resolve, reject) => { if (promises.length === 0) { //若是传入的参数是空的可迭代对象 return resolve([]); } else { let result = []; let index = 0; let j = 0; for (let value of promises) { (function (i) { Promise.resolve(value).then(data => { result[i] = data; //保证顺序 index++; if (index === j) { //此时的j是length. resolve(result); } }, err => { //某个promise失败 reject(err); return; }); })(j) j++; //length } } }); }
测试代码:
let p2 = Promise.all({ a: 1, [Symbol.iterator]() { let index = 0; return { next() { index++; if (index == 1) { return { value: new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }), done: false } } else if (index == 2) { return { value: new Promise((resolve, reject) => { resolve(222); }), done: false } } else if(index === 3) { return { value: 3, done: false } }else { return { done: true } } } } } }); setTimeout(() => { console.log(p2) }, 200);
<script>
标签中增长 async
(html5) 或者 defer
(html4) 属性,脚本就会异步加载。<script src="../XXX.js" defer></script>
defer
和 async
的区别在于:
defer
要等到整个页面在内存中正常渲染结束(DOM 结构彻底生成,以及其余脚本执行完成),在window.onload 以前执行;async
一旦下载完,渲染引擎就会中断渲染,执行这个脚本之后,再继续渲染。defer
脚本,会按照它们在页面出现的顺序加载async
脚本不能保证加载顺序script
标签动态建立的 script
,设置 src
并不会开始下载,而是要添加到文档中,JS文件才会开始下载。
let script = document.createElement('script'); script.src = 'XXX.js'; // 添加到html文件中才会开始下载 document.body.append(script);
let xhr = new XMLHttpRequest(); xhr.open("get", "js/xxx.js",true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { eval(xhr.responseText); } }
ES6 为数组实例新增了 flat
方法,用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数组没有影响。
flat
默认只会 “拉平” 一层,若是想要 “拉平” 多层的嵌套数组,须要给 flat
传递一个整数,表示想要拉平的层数。
function flattenDeep(arr, deepLength) { return arr.flat(deepLength); } console.log(flattenDeep([1, [2, [3, [4]], 5]], 3));
当传递的整数大于数组嵌套的层数时,会将数组拉平为一维数组,JS能表示的最大数字为 Math.pow(2, 53) - 1
,所以咱们能够这样定义 flattenDeep
函数
function flattenDeep(arr) { //固然,大多时候咱们并不会有这么多层级的嵌套 return arr.flat(Math.pow(2,53) - 1); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
function flattenDeep(arr){ return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
function flattenDeep(input) { const stack = [...input]; const res = []; while (stack.length) { // 使用 pop 从 stack 中取出并移除值 const next = stack.pop(); if (Array.isArray(next)) { // 使用 push 送回内层数组中的元素,不会改动原始输入 original input stack.push(...next); } else { res.push(next); } } // 使用 reverse 恢复原数组的顺序 return res.reverse(); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));
ES6 规定,默认的 Iterator
接口部署在数据结构的 Symbol.iterator
属性,换个角度,也能够认为,一个数据结构只要具备 Symbol.iterator
属性(Symbol.iterator
方法对应的是遍历器生成函数,返回的是一个遍历器对象),那么就能够其认为是可迭代的。
Symbol.iterator
属性,Symbol.iterator()
返回的是一个遍历器对象for ... of
进行循环let arry = [1, 2, 3, 4]; let iter = arry[Symbol.iterator](); console.log(iter.next()); //{ value: 1, done: false } console.log(iter.next()); //{ value: 2, done: false } console.log(iter.next()); //{ value: 3, done: false }
Iterator
接口的数据结构:上面咱们说,一个对象只有具备正确的 Symbol.iterator
属性,那么其就是可迭代的,所以,咱们能够经过给对象新增 Symbol.iterator
使其可迭代。
let obj = { name: "Yvette", age: 18, job: 'engineer', *[Symbol.iterator]() { const self = this; const keys = Object.keys(self); for (let index = 0; index < keys.length; index++) { yield self[keys[index]];//yield表达式仅能使用在 Generator 函数中 } } }; for (var key of obj) { console.log(key); //Yvette 18 engineer }
[1] MDN Promise.all
[2] Promise
[3] Iterator
谢谢各位小伙伴愿意花费宝贵的时间阅读本文,若是本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的确定是我前进的最大动力。 https://github.com/YvetteLau/...
推荐关注本人公众号: