端点笔试题
感觉这家公司的有几道题质量挺高的,但是有个问题是…有几道题居然重复了,这…
编程题
按照顺序加载多个JS文件如a,b,c,d,并且按照顺序依次插入到页面中,等到所有的JS文件加载完再执行Y。
考察:Promise理解,函数封装,onload事件处理。
(当时憋尿憋坏了…用了一个Promise.all,不过这道题出的挺好的)
当时的思路
- 将所有任务包装成promise对象放入数组,然后传入Proimse.All().then;
- 每个promise对象将script标签压入数组;
- promise.All().then(将script标签插入页面).then(执行y)
模拟当时的代码
let srcCon = ["node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js"];
let scriptCon = [];
let proCon = [];
for (let i = 0; i < srcContainer.length; i++) {
let script = document.createElement('script');
let promise = new Promise(resolve => {
// 1
script.onload = function () {
console.log('script' + i + 'loaded');
resolve();
}
}).then(() => {
// script标签放入数组
scriptCon.push(script);
});
// 将promise对象压入数组
proCon.push(promise);
script.src = srcContainer[i];
}
Promise.all(proCon).then(() => {
// 2
for (let i = 0; i < scriptCon.length; i++) {
document.body.appendChild(scriptCon[i]);
}
}).then(() => {
console.log('y');
})
这里犯了一个很严重的错误,标记1和2的地方。
-
onload
的方法是在页面插入script标签之后才会执行的。-> 在new中的回调直接push script标签
-> 将插入标签的操作提到外面。
let srcCon = ["node_modules/_vue@2.6.12@vue/dist/vue.js", "node_modules/_vue@2.6.12@vue/dist/vue.js", "node_modules/_vue@2.6.12@vue/dist/vue.js", "node_modules/_vue@2.6.12@vue/dist/vue.js", "node_modules/_vue@2.6.12@vue/dist/vue.js", "node_modules/_vue@2.6.12@vue/dist/vue.js"]; let scriptCon = []; let proCon = []; for (let i = 0; i < srcCon.length; i++) { let script = document.createElement('script'); let promise = new Promise(resolve => { script.onload = function () { console.log('script' + i + 'loaded'); resolve(); } scriptCon.push(script); }) proCon.push(promise); script.src = srcCon[i]; } for (let i = 0; i < scriptCon.length; i++) { document.body.appendChild(scriptCon[i]); } Promise.all(proCon).then(() => { console.log('y'); });
-
第二点也是非常非常重要的一点,Promise.all是并行执行各个异步事件的,也就是说,不能保证执行的先后顺序。
-
我们在resolve中传入每次参与遍历的变量的i
Promise.all(proCon).then(num => { console.log(num); }); //[0, 1, 2, 3, 4, 5]
可以看到,promise会为我们返回一个数组,这个数组的结果是按照all中的数组放入的。
小结
load
事件在整个页面及所有依赖资源如样式表和图片都已完成加载时触发。Promise.all
会并行执行多个异步任务,但是不能保证执行顺序,返回的结果是按照顺序的。
反思
- 其实将script标签放入数组统一插入body并不会节省性能,因为根本不会触发重绘和回流。
- 因此应该有多少个src就创建多少个任务,等到上一个任务结束再执行下一个。
let srcCon = ["node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js",
"node_modules/_vue@2.6.12@vue/dist/vue.js"];
let promise = Promise.resolve();
promise.then(() => {
return new Promise(resolve => {
let script = document.createElement('script');
script.onload = function () {
console.log(1);
resolve();
}
script.src = srcCon[0];
document.body.appendChild(script);
})
}).then(() => {
return new Promise(resolve => {
let script = document.createElement('script');
script.onload = function () {
console.log(2);
resolve();
}
script.src = srcCon[0];
document.body.appendChild(script);
})
}).then(() => {
return new Promise(resolve => {
let script = document.createElement('script');
script.onload = function () {
console.log(3);
resolve();
}
script.src = srcCon[0];
document.body.appendChild(script);
})
}).then(() => {
return new Promise(resolve => {
let script = document.createElement('script');
script.onload = function () {
console.log(4);
resolve();
}
script.src = srcCon[0];
document.body.appendChild(script);
})
})
思路:每次都生成一个新的Promise对象管控下一个then中的回调,只有上一个script执行了load,加载完成之后才执行下一个任务。
当然了,这么冗余的代码肯定是不行的。
let promise = Promise.resolve();
function insertScript(src, i) {
return new Promise(resolve => {
let script = document.createElement('script');
script.onload = function () {
console.log(i);
resolve();
}
script.src = srcCon[i];
document.body.appendChild(script);
})
}
for (let i = 0; i < srcCon.length; i++) {
promise = promise.then(() => {
return insertScript(srcCon[i], i);
});
}
promise.then(() => {
console.log('y');
})
吃饭!