目录
一、setTimeout、Promise、Async/Await 的区别?
四、Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
五、介绍下 Promise.all 使用、原理实现及错误处理?
六、['1', '2', '3'].map(parseInt) 输出什么?为什么?
八、简单改造下面的代码,使之分别打印 10 和 20?(接着上题来个变式问题)
十、使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果
前言
前端面试问题,可能问题不是很全面,但基本上是常见的且自我补充的一个过程,相信从中可以完善自己。另外部分见解答案解析来自github博客等资料整理过程中如有错误欢迎指出!
一、setTimeout、Promise、Async/Await 的区别?
首先看到这个题目,我就想到了宏任务队列和微任务队列;我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
1. setTimeout
console.log('script start') //1. 打印 script start
setTimeout(function(){
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
2. Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
当JS主线程执行到Promise对象时,
-
promise1.then() 的回调就是一个 task
-
promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
-
promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
-
setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况
3. 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');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
举个例子:
async function func1() {
return 1
}
console.log(func1())
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。
func1().then(res => {
console.log(res); // 30
})
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
- 宏任务: setTimeout,setInterval
- 微任务: promise,requestAnimation,MutationObserve
这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
查询浏览器支持的程度,先后执行Promise
> MutationObserver
> setImmediate
> setTimeout
二、Async/Await 如何通过同步的方式实现异步
Async/Await是一个期待已久的JavaScript特性,让我们更好的理解使用异步函数。它建立在Promises上,并且与所有现有的基于Promise的API兼容。
首先javascript语言是一门“单线程”的语言
通俗的讲就是,执行代码是一行一行的往下走(即所谓的同步),
如果上面的没执行完,就痴痴的等着。
异步,即 发起网络请求(诸如 IO 操作,定时器),由于需要等服务器响应,就先不理会,而是去做其他的事儿,等请求返回了结果的时候再说(即异步)
Async/Await
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
1. async 起什么作用
这个问题的关键在于,async 函数是怎么处理它的返回值的!
我们当然希望它能直接通过 return
语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);
输出的是一个 Promise 对象。
所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return
一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then()
链来处理这个 Promise 对象,就像这样
testAsync().then(v => {
console.log(v); // 输出 hello async
});
现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)
。
联想一下 Promise 的特点——无等待,所以在没有 await
的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
2. await
await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。
await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;
如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
function getSomething() {
return "something";
}async function testAsync() {
return Promise.resolve("hello async");
}async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}test();
-------------------------------------------------------分割线----------------------------------------------------------------
async/await
是参照 Generator
封装的一套异步处理方案,可以理解为 Generator
的语法糖,
Generator之所以可以通过同步实现异步是它具有暂停执行和恢复执行的特性和函数体内外的数据交换和错误处理机制。
所以了解 async/await
就不得不讲一讲 Generator
,
而 Generator
又依赖于迭代器Iterator
,
所以就得先讲一讲 Iterator
,
而 Iterator
的思想呢又来源于单向链表,
终于找到源头了:单向链表
1. 单向链表
wiki:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序储存数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序储存,链表在插入的时候可以达到 o(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 o(n)的时间,而顺序表响应的时间复杂度分别是 o(logn)和 o(1)。
总结一下链表优点:
- 无需预先分配内存
- 插入/删除节点不影响其他节点,效率高(典型的例子:git commit、dom 操作)
单向链表:是链表中最简单的一种,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接
单链特点:节点的链接方向是单向的;相对于数组来说,单链表的的随机访问速度较慢,但是单链表删除/添加数据的效率很高。
理解 js 原型链/作用域链的话,理解这个很容易,他们是相通的。编程语言中,数组的长度时固定的,所以数组中的增加和删除比较麻烦,需要频繁的移动数组中的其他元素,而 js 作为一门动态语言,数组本质是一个类似数组的对象,是动态的,不需要预先分配内存
那么如何设计一个单向链表呢?这个取决于我们需要哪些操作,通常有:
- append(element):追加节点
- insert(element,index):在索引位置插入节点
- remove(element):删除第一个匹配到的节点
- removeAt(index):删除指定索引节点
- removeAll(element):删除所有匹配的节点
- get(index):获取指定索引的节点信息
- set(element,index):修改指定索引的节点值
- indexOf(element):获取某节点的索引位置
- clear():清除所有节点
- length():返回节点长度
- printf():打印节点信息
2. Iterator
Iterator
翻译过来就是**迭代器(遍历器)**让我们先来看看它的遍历过程(类似于单向链表):
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用指针对象的
next
方法,将指针指向数据结构的第一个成员 - 第二次调用指针对象的
next
方法,将指针指向数据结构的第二个成员 - 不断的调用指针对象的
next
方法,直到它指向数据结构的结束位置
一个对象要变成可迭代的,必须实现 @@iterator
方法,即对象(或它原型链上的某个对象)必须有一个名字是 Symbol.iterator
的属性(原生具有该属性的有:字符串、数组、类数组的对象、Set 和 Map):
属性 | 值 |
---|---|
[Symbol.iterator]: | 返回一个对象的无参函数,被返回对象符合迭代器协议 |
当一个对象需要被迭代的时候(比如开始用于一个 for..of
循环中),它的 @@iterator
方法被调用并且无参数,然后返回一个用于在迭代中获得值的迭代器
迭代器协议:产生一个有限或无限序列的值,并且当所有的值都已经被迭代后,就会有一个默认的返回值
当一个对象只有满足下述条件才会被认为是一个迭代器:
它实现了一个 next()
的方法,该方法必须返回一个对象,对象有两个必要的属性:
done
(bool)- true:迭代器已经超过了可迭代次数。这种情况下,value 的值可以被省略
- 如果迭代器可以产生序列中的下一个值,则为 false。这等效于没有指定 done 这个属性
value
迭代器返回的任何 JavaScript 值。done 为 true 时可省略
根据上面的规则,咱们来自定义一个简单的迭代器:
const makeIterator = arr => {
let nextIndex = 0;
return {
next: () =>
nextIndex < arr.length
? { value: arr[nextIndex++], done: false }
: { value: undefined, done: true },
};
};
const it = makeIterator(['人月', '神话']);
console.log(it.next()); // { value: "人月", done: false }
console.log(it.next()); // { value: "神话", done: false }
console.log(it.next()); // {value: undefined, done: true }
我们还可以自定义一个可迭代对象:
const myIterable = {};
myIterable[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
};
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
//or
console.log([...myIterable]); // [1, 2, 3]
3. Generator
Generator
:生成器对象是生成器函数(GeneratorFunction)返回的,它符合可迭代协议和迭代器协议,既是迭代器也是可迭代对象,可以调用 next
方法,但它不是函数,更不是构造函数
生成器函数(GeneratorFunction):
function* name([param[, param[, ... param]]]) { statements }
- name:函数名
- param:参数
- statements:js 语句
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器对象,当这个迭代器的 next()
方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现 yield
的位置为止(让执行处于暂停状),yield
后紧跟迭代器要返回的值。或者如果用的是 yield*
(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行),调用 next()
(再启动)方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield
语句的返回值,例如:
function* another() { yield '人月神话'; } function* gen() { yield* another(); // 移交执行权 const a = yield 'hello'; const b = yield a; // a='world' 是 next('world') 传参赋值给了上一个 yidle 'hello' 的左值 yield b; // b=! 是 next('!') 传参赋值给了上一个 yidle a 的左值 } const g = gen(); g.next(); // {value: "人月神话", done: false} g.next(); // {value: "hello", done: false} g.next('world'); // {value: "world", done: false} 将 'world' 赋给上一条 yield 'hello' 的左值,即执行 a='world', g.next('!'); // {value: "!", done: false} 将 '!' 赋给上一条 yield a 的左值,即执行 b='!',返回 b g.next(); // {value: undefined, done: false}
看到这里,你可能会问,Generator
和 callback
有啥关系,如何处理异步呢?其实二者没有任何关系,我们只是通过一些方式强行的它们产生了关系,才会有 Generator
处理异步
我们来总结一下 Generator
的本质,暂停,它会让程序执行到指定位置先暂停(yield
),然后再启动(next
),再暂停(yield
),再启动(next
),而这个暂停就很容易让它和异步操作产生联系,因为我们在处理异步时:开始异步处理(网络求情、IO 操作),然后暂停一下,等处理完了,再该干嘛干嘛。不过值得注意的是,js 是单线程的(又重复了三遍),异步还是异步,callback 还是 callback,不会因为 Generator
而有任何改变
下面来看看,用 Generator
实现异步:
const promisify = require('util').promisify; const path = require('path'); const fs = require('fs'); const readFile = promisify(fs.readFile); const gen = function*() { const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); }; const g = gen(); const g1 = g.next(); console.log('g1:', g1); g1.value .then(res1 => { console.log('res1:', res1); const g2 = g.next(res1); console.log('g2:', g2); g2.value .then(res2 => { console.log('res2:', res2); g.next(res2); }) .catch(err2 => { console.log(err2); }); }) .catch(err1 => { console.log(err1); }); // g1: { value: Promise { <pending> }, done: false } // res1: { // "a": 1 // } // { // "a": 1 // } // g2: { value: Promise { <pending> }, done: false } // res2: { // "b": 2 // } // { // "b": 2 // }
以上代码是 Generator
和 callback
结合实现的异步,可以看到,仍然需要手动执行 .then
层层添加回调,但由于 next()
方法返回对象 {value: xxx,done: true/false}
所以我们可以简化它,写一个自动执行器:
const promisify = require('util').promisify; const path = require('path'); const fs = require('fs'); const readFile = promisify(fs.readFile); function run(gen) { const g = gen(); function next(data) { const res = g.next(data); // 深度递归,只要 `Generator` 函数还没执行到最后一步,`next` 函数就调用自身 if (res.done) return res.value; res.value.then(function(data) { next(data); }); } next(); } run(function*() { const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); // { // "a": 1 // } const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); // { // "b": 2 // } });
async function
代替了 function*
,await
代替了 yield
,同时也无需自己手写一个自动执行器 run
了
现在再来看看async/await
的特点:
- 当
await
后面跟的是 Promise 对象时,才会异步执行,其它类型的数据会同步执行 - 执行
const res = readFile();
返回的仍然是个 Promise 对象,上面代码中的return 'done';
会直接被下面then
函数接收到
res.then(data => { console.log(data); // done });
三、异步笔试题,请写出下面代码的运行结果
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');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/* script start async1 start async2 promise1 script end async1 end promise2 setTimeout */
这道题主要考察的是事件循环中函数执行顺序的问题,其中包括async
,await
,setTimeout
,Promise
函数。这些问题在上面的文章里面都有提及任务队列 以及宏任务微任务。
四、Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
首先这题应该是和上一题内容相关,先来看一段代码
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
//执行结果是:1 2 4 3
在扩展一下
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve(5);
console.log(2);
}).then(val => {
console.log(val);
});
promise.then(() => {
console.log(3);
});
console.log(4);
setTimeout(function() {
console.log(6);
});
//执行结果: 1 2 4 5 3 6
Promise new的时候会立即执行里面的代码 then是微任务 会在本次任务执行完的时候执行 setTimeout是宏任务 会在下次任务执行的时候执行。Promise必然是同步的。then:
在ES6时代有了微异步的设定,then作为最典型代表,算是异步的一员。
在ES5时代,实现then的方式则要看构造函数里resolve(或reject)的用法了,如果resolve被同步使用,实质上resolve仍然是同步的。总的来说:then()当然是同步执行,只不过是.then的cb被放入了微任务队列,产生了异步执行
五、介绍下 Promise.all 使用、原理实现及错误处理?
1、Promise概念
Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。Promise.all()接受一个由promise任务组成的数组,可以同时处理多个promise任务,当所有的任务都执行完成时,Promise.all()返回resolve,但当有一个失败(reject),则返回失败的信息,即使其他promise执行成功,也会返回失败。和后台的事务类似。和rxjs中的forkJoin方法类似,合并多个 Observable 对象 ,等到所有的 Observable 都完成后,才一次性返回值。
2、Promise.all如何使用
对于 Promise.all(arr) 来说,在参数数组中所有元素都变为决定态后,然后才返回新的 promise。
// 以下 demo,请求两个 url,当两个异步请求返还结果后,再请求第三个 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)
Promise.all([p1, p2])
.then((datas) => { // 此处 datas 为调用 p1, p2 后的结果的数组
return request(`http://some.url.3?a=${datas[0]}&b=${datas[1]}`)
})
.then((data) => {
console.log(msg)
})
3、Promise.all原理实现
function promiseAll(promises){ return new Promise(function(resolve,reject){ if(!Array.isArray(promises)){ return reject(new TypeError("argument must be anarray")) } var countNum=0; var promiseNum=promises.length; var resolvedvalue=new Array(promiseNum); for(var i=0;i<promiseNum;i++){ (function(i){ Promise.resolve(promises[i]).then(function(value){ countNum++; resolvedvalue[i]=value; if(countNum===promiseNum){ return resolve(resolvedvalue) } },function(reason){ return reject(reason) ) })(i) } }) } var p1=Promise.resolve(1), p2=Promise.resolve(2), p3=Promise.resolve(3); promiseAll([p1,p2,p3]).then(function(value){ console.log(value) })
4、Promise.all错误处理
有时候我们使用Promise.all()执行很多个网络请求,可能有一个请求出错,但我们并不希望其他的网络请求也返回reject,要错都错,这样显然是不合理的。如何做才能做到promise.all中即使一个promise程序reject,promise.all依然能把其他数据正确返回呢?
全部改为串行调用(失去了node 并发优势)
当promise捕获到error 的时候,代码吃掉这个异常,返回resolve,约定特殊格式表示这个调用成功了
var p1 =new Promise(function(resolve,reject){ setTimeout(function(){ resolve(1); },0) }); var p2 = new Promise(function(resolve,reject){ setTimeout(function(){ resolve(2); },200) }); var p3 = new Promise(function(resolve,reject){ setTimeout(function(){ try{ console.log(XX.BBB); } catch(exp){ resolve("error"); } },100) }); Promise.all([p1, p2, p3]).then(function (results) { console.log("success") console.log(results); }).catch(function(r){ console.log("err"); console.log(r); });
最后补充 如果无论成功或失败都执行 resolve,可以用 Promise.allSettled
六、['1', '2', '3'].map(parseInt) 输出什么?为什么?
输出:1,NaN,NaN;
这个问题先从map看:
map
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
var new_array = arr.map(function callback(currentValue[,index[, array]]) {
// Return element for new_array
}[, thisArg])
可以看到callback
回调函数需要三个参数, 我们通常只使用第一个参数 (其他两个参数是可选的)。currentValue
是callback 数组中正在处理的当前元素。index
可选, 是callback 数组中正在处理的当前元素的索引。array
可选, 是callback map 方法被调用的数组。
另外还有thisArg
可选, 执行 callback 函数时使用的this 值。
然后再看parseInt:
parseInt
parseInt()
函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。
const intValue = parseInt(string[, radix]);
string
要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。
radix
一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。默认为10。返回值
返回一个整数或NaN
parseInt(100); // 100
parseInt(100, 10); // 100
parseInt(100, 2); // 4 -> converts 100 in base 2 to base 10
注意:
在radix
为 undefined,或者radix
为 0 或者没有指定的情况下,JavaScript 作如下处理:
- 如果字符串 string 以"0x"或者"0X"开头, 则基数是16 (16进制).
- 如果字符串 string 以"0"开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
- 如果字符串 string 以其它任何值开头,则基数是10 (十进制)。
最终代码可以修改为:
['1', '2', '3'].map((item, index) => { return parseInt(item, index) })
输出:
parseInt('1', 0) // 1 parseInt('2', 1) // NaN parseInt('3', 2) // NaN, 3 不是二进制
变式:
['10','10','10','10','10'].map(parseInt);
// [10, NaN, 2, 3, 4]
parseInt("10",0) //10 10为 10进制=>10
parseInt("10",1) // NaN
parseInt("10",2) //2 10为 2进制=>10
parseInt("10",3) //10 10为 3进制=>10
parseInt("10",4) //10 10为 4进制=>10
七、下面的代码打印什么内容?为什么?
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
输出的是://function b()
ƒ b(){
b = 20;
console.log(b);
}
原因:
作用域:执行上下文中包含作用于链:
在理解作用域链之前,先介绍一下作用域,作用域可以理解为执行上下文中申明的变量和作用的范围;包括块级作用域/函数作用域;
特性:声明提前:一个声明在函数体内都是可见的,函数声明优先于变量声明;
在非匿名自执行函数中,函数变量为只读状态无法修改;
var b = 10;
(function b(){//这个是内部函数的作用域,函数执行会按照该作用域执行,变量b 赋值等于20;function b()又是一个具名函数;
b = 20;
console.log(b);})();
这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。第一部分是包围在 圆括号运算符
()
里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
第二部分再一次使用 ()
创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。
变式:
var b = 10;
(function b() {
window.b = 20;
console.log(b); // [Function b]
console.log(window.b); // 20是必然的
})();
var b = 10;
(function b() {
var b = 20; //是立即调用函数表达式(IIFE)的函数的内部变量
console.log(b); // 20
console.log(window.b); // 10
})();
八、简单改造下面的代码,使之分别打印 10 和 20?(接着上题来个变式问题)
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
打印10
1.
var b = 10;
(function b(){
b = 20;
//console.log(b); //[function]console.log(window.b);//10
})();
2.
var b = 10;
(function b(){
b = 20;
//console.log(b); //[function]console.log(this.b);//10
})();
3.
var b = 10; (function b(b) { b =20; //console.log(b);//20 })(b) console.log(b) //10
4.
var b = 10;
(function b(b) {
b = 20;
console.log(this.b) //10 window.b
})(b)
打印20
1.
var b = 10;
(function b(){
let b = 20;// var, const
console.log(b); //20
})();
2.
var b = 10;
(function b(b) {
b = 20;
console.log(b) //20
})(b)
上题如果理解了的话这题变式输出结果就很容易懂了;
九、下面代码输出什么?
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
undefind------>10 --------->20;
这个题跟上面的题目其实是相关的;也考察了函数作用域等问题;这里还有变量声明提升;那么解析起来就简单了;
首先定义了一个变量a = 10;在内部声名var a = 20;相当于先声明var a;然后再执行赋值操作,这是在IIFE(立即执行函数)内形成的独立作用域,然后因为变量声明提升,a = 5;
这条语句执行时,局部的变量a
已经声明,因此它产生的效果是对局部的变量a
赋值也就是函数域中,此时window.a
依旧是最开始赋值的10;
最后var a = 20,给局部作用域赋值,输出20.
如果把var = 20 注释了;那么a只有在外部有声明,显示的就是外部的A变量的值了;
十、使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果
我的答案:
[102, 15, 22, 29, 3, 8]
解析:
arr.sort([compareFunction])
sort()
方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的;
如果没有指明 compareFunction
,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。例如 "Banana" 会被排列到 "cherry" 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction
),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。
如果指明了 compareFunction
,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
- 如果
compareFunction(a, b)
小于 0 ,那么 a 会被排列到 b 之前;
- 如果
compareFunction(a, b)
等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
- 如果
compareFunction(a, b)
大于 0 , b 会被排列到 a 之前。 compareFunction(a, b)
必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
所以,比较函数格式如下:
function compare(a, b) {
if (a < b ) { // 按某种排序标准进行比较, a 小于 b
return -1;
}
if (a > b ) {
return 1;
}
// a must be equal to b
return 0;
}
Copy to Clipboard
要比较数字而非字符串,比较函数可以简单的以 a 减 b,如下的函数将会将数组升序排列
function compareNumbers(a, b) {
return a - b;
}
总结
如果你觉得这面试题对你有帮助,我想请你帮我个小忙:来一个点赞收藏三连击;
再次说明整理问题不易,如有内容的需要改进的请指出,以便及时修改!感谢!
学习过程中,祝大家早日取得合适offer!