We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。 每个周末我会仔细阅读大家的答案,整理最一份较优答案出来,因本人水平有限,有误的地方,大家及时指正。
Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,每个工作日发布一道面试题。
2019-05-20
每个周末我会仔细阅读大家的答案,整理最一份较优答案出来,因本人水平有限,有误的地方,大家及时指正。
如果想 加群 学习,可以通过文末的公众号,添加我为好友。
本周面试题一览:
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
function foo() { var a = 2; return function fn() { console.log(a); } } let func = foo(); func(); //输出2
闭包使得函数可以继续访问定义时的词法作用域。拜 fn 所赐,在 foo() 执行后,foo 内部作用域不会被销毁。
无论通过何种手段将内部函数传递到所在的词法作用域之外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。如:
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》)
闭包会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
在实现 Promise.all 方法之前,我们首先要知道 Promise.all 的功能和特点,因为在清楚了 Promise.all 功能和特点的情况下,我们才能进一步去写实现。
Promise.all(iterable) 返回一个新的 Promise 实例。此实例在 iterable 参数内所有的 promise 都 fulfilled 或者参数中不包含 promise 时,状态变成 fulfilled;如果参数中 promise 有一个失败rejected,此实例回调失败,失败原因的是第一个失败 promise 的返回结果。
Promise.all(iterable)
iterable
promise
fulfilled
rejected
let p = Promise.all([p1, p2, p3]);
p的状态由 p1,p2,p3决定,分成以下;两种情况:
(1)只有p1、p2、p3的状态都变成 fulfilled,p的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.all 的返回值是一个 promise 实例
Promise.all
Promise.all 返回的 promise 的状态
Promise.all = function (promises) { return new Promise((resolve, reject) => { //Array.from 将可迭代对象转换成数组 promises = Array.from(promises); 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; }); } } }); }
<script>
async
defer
<script src="../XXX.js" defer></script>
defer 和 async 的区别在于:
script
动态创建的 script ,设置 src 并不会开始下载,而是要添加到文档中,JS文件才会开始下载。
src
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 默认只会 “拉平” 一层,如果想要 “拉平” 多层的嵌套数组,需要给 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 函数
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 方法对应的是遍历器生成函数,返回的是一个遍历器对象),那么就可以其认为是可迭代的。
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 }
上面我们说,一个对象只有具有正确的 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] 珠峰架构课(墙裂推荐)
[1] MDN Promise.all
[2] Promise
[3] Iterator
谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的最大动力。 https://github.com/YvetteLau/Blog
关注公众号,加入技术交流群。
The text was updated successfully, but these errors were encountered:
插眼
Sorry, something went wrong.
数组 flat 不是有Infinity,为什么要这么写Math.pow(2,53) - 1
No branches or pull requests
关于【Step-By-Step】
15. 什么是闭包?闭包的作用是什么?
什么是闭包?
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
创建一个闭包
闭包使得函数可以继续访问定义时的词法作用域。拜 fn 所赐,在 foo() 执行后,foo 内部作用域不会被销毁。
无论通过何种手段将内部函数传递到所在的词法作用域之外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。如:
闭包的作用
能够访问函数定义时所在的词法作用域(阻止其被回收)。
私有化变量
模块模式具有两个必备的条件(来自《你不知道的JavaScript》)
闭包的缺点
闭包会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
16. 实现 Promise.all 方法
在实现 Promise.all 方法之前,我们首先要知道 Promise.all 的功能和特点,因为在清楚了 Promise.all 功能和特点的情况下,我们才能进一步去写实现。
Promise.all 功能
Promise.all(iterable)
返回一个新的 Promise 实例。此实例在iterable
参数内所有的promise
都fulfilled
或者参数中不包含promise
时,状态变成fulfilled
;如果参数中promise
有一个失败rejected
,此实例回调失败,失败原因的是第一个失败promise
的返回结果。p的状态由 p1,p2,p3决定,分成以下;两种情况:
(1)只有p1、p2、p3的状态都变成
fulfilled
,p的状态才会变成fulfilled
,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3之中有一个被
rejected
,p的状态就变成rejected
,此时第一个被reject的实例的返回值,会传递给p的回调函数。Promise.all 的特点
Promise.all
会 同步 返回一个已完成状态的promise
Promise.all
会 异步 返回一个已完成状态的promise
Promise.all
返回一个 处理中(pending) 状态的promise
.Promise.all
返回的promise
异步地变为完成。promise
失败,Promise.all
异步地将失败的那个结果给失败状态的回调函数,而不管其它promise
是否完成Promise.all
返回的promise
的完成状态的结果都是一个数组Promise.all 实现
17. 异步加载 js 脚本的方法有哪些?
<script>
标签中增加async
(html5) 或者defer
(html4) 属性,脚本就会异步加载。defer
和async
的区别在于:defer
要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),在window.onload 之前执行;async
一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。defer
脚本,会按照它们在页面出现的顺序加载async
脚本不能保证加载顺序动态创建
script
标签动态创建的
script
,设置src
并不会开始下载,而是要添加到文档中,JS文件才会开始下载。XHR 异步加载JS
18. 请实现一个 flattenDeep 函数,把嵌套的数组扁平化
利用 Array.prototype.flat
ES6 为数组实例新增了
flat
方法,用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数组没有影响。flat
默认只会 “拉平” 一层,如果想要 “拉平” 多层的嵌套数组,需要给flat
传递一个整数,表示想要拉平的层数。当传递的整数大于数组嵌套的层数时,会将数组拉平为一维数组,JS能表示的最大数字为
Math.pow(2, 53) - 1
,因此我们可以这样定义flattenDeep
函数利用 reduce 和 concat
使用 stack 无限反嵌套多层嵌套数组
19. 可迭代对象有什么特点
ES6 规定,默认的
Iterator
接口部署在数据结构的Symbol.iterator
属性,换个角度,也可以认为,一个数据结构只要具有Symbol.iterator
属性(Symbol.iterator
方法对应的是遍历器生成函数,返回的是一个遍历器对象),那么就可以其认为是可迭代的。可迭代对象的特点
Symbol.iterator
属性,Symbol.iterator()
返回的是一个遍历器对象for ... of
进行循环原生具有
Iterator
接口的数据结构:自定义一个可迭代对象
上面我们说,一个对象只有具有正确的
Symbol.iterator
属性,那么其就是可迭代的,因此,我们可以通过给对象新增Symbol.iterator
使其可迭代。参考文章:
[1] 珠峰架构课(墙裂推荐)
[1] MDN Promise.all
[2] Promise
[3] Iterator
谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的最大动力。 https://github.com/YvetteLau/Blog
The text was updated successfully, but these errors were encountered: