引言
本博客旨在学习记录,请指正,勿苛责
面题来源:前端JS选择题
1.请问JS中的基本类型有几种?
- A.5
- B.6
- C.7
答案
答案: C
解析:JS的数据类型共8种,ES5,6种:Boolean、undefined、Null、String、Number、object。ES6 中新增了一种 Symbol 。这种类型的对象是唯一的,目的是为了解决属性名冲突的问题,作为标记。
谷歌67版本中还出现了一种 bigInt。是指安全存储、操作大整数,但是很多人不把这个做为一个类型,并且暂时没有进入ES版本中
2.下面代码的输出是什么?
function sayHi(){
console.log(name);
console.log(age);
var name = "TJH";
let age = 24;
}
sayHi();
- A.TJH和undefined
- B.TJH和ReferenceError
- C.ReferenceError和24
- undefined和ReferenceError
答案
答案: D
解析:在函数内部,我们首先通过 var 关键字声明了 name 变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined 。因此当我们打印 name 变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined。
通过 let 和 const 关键字声明的变量也会提升,但是和 var 不同,它们不会被初始化。在我们声明(初始化)之前是不能访问它们的。这个行为被称之为暂时性死区。当我们试图在声明之前访问它们时,JavaScript 将会抛出一个 ReferenceError 错误。
思考:let和var的区别,1.var变量会发生变量提升,let则不会进行变量提升,即未声明变量的调用表现不同,var:undefined,let:报错ReferenceError,这边会有一个暂时性死区的约束;2.重复声明同一变量,var:后声明的覆盖前声明的,let:报错SyntaxError,即只能声明一次;3.作用域不同,var是全局作用域,即声明后均可调用,let是块级作用域,只有当前块级环境可以调用
PS:JS环境中程序先寻找当前块级环境,找不到再逐层向外寻找
3.下面代码的输出是什么?
for(var i =0 ;i<3;i++){
setTimeout(()=>console.log(i),1)
}
for(let i =0;i<3;i++){
setTimeout(()=>console.log(i),1)
}
- A. 0 1 2 and 0 1 2
- B. 0 1 2 and 3 3 3
- C. 3 3 3 and 0 1 2
答案
答案: C
解析:由于 JavaScript 的事件循环,setTimeout 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 setTimeout 回调执行的时候,i 的值等于 3。
在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 let 和 const 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。
思考:我的理解是第一个函数的 i 是一块并且跟着循环(被调用了),function是一块(循环) ,setTimeout是一块,而第二个函数的 function是一块,i 和setTimeout是一块,每次循环丢到一个地方(堆/栈?)等着执行,题目呢,我大概是这样理解的,但是上面的事件循环有必要进行深入地了解。
4.下面代码的输出是什么?
let a = 666;
let b = new Number(666);
let c = 666;
console.log(a == b);
console.log(a === b);
console.log(b === c);
- A. true false true
- B. false false true
- C. true false false
- D.false true true
答案
答案: C
解析:new Number() 是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一堆额外的功能并且它是一个对象。
当我们使用 == 操作符时,它只会检查两者是否拥有相同的值。因为它们的值都是 3,因此返回 true。
然后,当我们使用 === 操作符时,两者的值以及类型都应该是相同的。new Number() 是一个对象而不是 number,因此返回 false。
思考:这一块比较好理解,new Number() 是一个对象,它会输出一个值。 == 比较的是值,而===比较的不仅是值,还有类型、引用的内存地址等是否都相等,所以不相等
5.下面代码的输出是什么?
const a = {};
const b= { key:"b" };
const c = { key :"c" };
a[b] = 123 ;
a[c] = 456;
console.log(a[b]);
- A.123
- B.456
- C.undefined
- D.ReferenceError
答案
答案: B
解析:用对象作为key,先会被隐式转换为字符串,其值为[object object],所以a[b]其实是a[object object]后面的a[c]也是一样,转换后的key值一样,所以被重写,值为456.
思考:我尝试了一下function也与上述相同,字符串和Number正常被重写,是不是基本类型的会被重写,引用类型的则跟上述一样,这只是我的猜测,未得到明确的答案
6.下面代码的输出是什么?
const numbers = [1,2,3];
numbers[10] = 11;
console.log(numbers);
- A.[1,2,3,7x null,11]
- B.[1,2,3,11]
- C.[1,2,3,7x empty,11]
- D.SyntaxError
答案
答案: C
解析:数组中的empty,即空元素,相当于undefined,但是使用forEach方法会直接跳过,forEach()方法按升序为数组中含有有效值的每一项执行一次callback函数,哪些已删除或者未初始化的项将被跳过。可以用for循环解决这个问题:
for(let number of numbers){
console.log(number)
}
此时会打印出1、2、3、7个undefined、11
7.下面代码的输出是什么?
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
- A.1 1 2
- B.1 2 2
- C.0 2 2
- D.0 1 2
答案
答案: C
解析:这个知识点比较简单,JS的算数运算符,++在后先输出再计算,++在前先计算再输出
8.下面代码的输出是什么?
let obj1 = {
name:'obj1_name',
print:function(){
return ()=>console.log(this.name)
}
}
let obj2 = {name:'obj2_name'}
obj1.print()()
obj1.print().call(obj2)
obj1.print.call(obj2)()
- A.obj1_name obj2_name obj2_name
- B.obj2_name obj1_name obj2_name
- C.obj1_name obj1_name obj2_name
答案
答案: C
解析:先看下第一个调用:拆开两部分,第一部分是obj.print,第二部分是()(),第二部分分别未两个匿名函数function(){}以及()=>{},当执行obj.print()()时,this的指向是print函数的调用者,也就是obj1,所以输出'obj1_name'
第二个调用:与第一个调用相同,不同点是call()无法改变this的指向,因为是箭头函数,所以输出的仍然是'obj1_name'
第三个调用:我们只需要看前面一部分,obj.print.call(obj2),注意这里先是改变调用然后再执行函数,因为这边调用的先是属性然后才是方法的执行,翻译过来也就是obj2.print(),然后再接上(),根据箭头函数的定义,这里this的指向是obj2,所以输出'obj2_name'
9.下面代码的输出是什么?
const obj = { 1:"a", 2:"b",3:"c"};
const set = new Set ([1,2,3,4,5]);
obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
- A. false true false true
- B.false true true true
- C.true true false true
- D.true true true true
答案
答案: C
解析:与5的知识点有点关系,Number作为key的时候会被隐式转换成字符串,所以前两个是一样的,都输出true,那么下面的set方法我就不多赘述了,false true 因为一个是字符串,一个是Number
10.下面代码的输出是什么?
function Foo(){
getName = function (){console.log(1)}
return this;
}
Foo.getName = function(){console.log(2)}
Foo.prototype.getName = function(){console.log(3)};
var getName = function(){console.log(4)}
function getName(){ console.log(5) }
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
- A.4 2 1 1 2 3 3
- B.2 1 4 1 2 3 3
- C.2 4 1 1 3 2 3
- D.2 4 1 1 2 3 3
答案
答案: D
解析:这是一个比较复杂的点,包含原型链、函数声明(function)与函数表达式(var ...)的区别、new关键字,先来看下,new关键字做了什么事:
1.创建一个新的对象,这个对象的类型是object;
2.设置这个新的对象的内部、可访问性和prototype属性为构造函数
3.执行构造函数、当this关键字被提及的时候,使用新创建的对象的属性,返回新创建的对象
4.在创建对象成功之后,如果调用一个新对象没有属性的时候,javaScript会延原型链向止逐层查找对应的内容,这类似于传统的类继承。
下面我们来看调用,
Foo.getName()//这边输出2没什么问题
getName();//函数声明会提前,所以function getName()会被var getName()覆盖,所以输出4,对于这个仍有疑问可以参考:函数声明与函数表达式
Foo().getName()//这个输出1没什么问题,同时函数外的getName方法又被覆盖了,因为下面的return this
getName();//由于上面,所以输出1
new Foo.getName() //首先看运算优先级把,new Foo() >Foo() > new Foo ,先运算Foo.getName = function(){}结果为2,再new一个Foo的实例对象,进行输出2
new Foo().getName() //先执行new Foo(),构建了一个新的实例对象,并继承了Foo()这个构造函数中的getName方法,再执行Foo.prototype.getName ,所以输出了3
new new Foo().getName() //同样先执行new Foo(),变成了new Foo实例对象.getName(),然后再执行 Foo实例对象.getName(),又回到了Foo.prototype.getName,所以还是输出3;关于这一块有疑虑可以参考:new
11.下面代码的输出是什么?
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('setTimeout0')
},0)
setTimeout(function(){
console.log('setTimeout3')
},3)
setImmediate(()=>console.log('setImmediate'));
process.nextTick(()=>console.log('nextTick'));
async1();
new Promise(function(resolve){
console.log('promise1');
resolve();
console.log('promise2');
}).then(function(){
console.log('promise3');
})
console.log('script end')
- A.script start - async2 start - async1 - promise1 - promise2 -script end - nextTick - async1 end - promise3 - setTimeout0 - setImmediate - setTimeout3
- B.script start - async1 start - async2 - promise2 - promise1 -script end - nextTick - async1 end - promise3 - setTimeout0 - setImmediate - setTimeout3
- C.script start - async1 start - async2 - promise1 - promise2 -script end - nextTick - async1 end - promise3 - setTimeout3 - setImmediate - setTimeout0
- D.script start - async1 start - async2 - promise1 - promise2 -script end - nextTick - async1 end - promise3 - setTimeout0 - setImmediate - setTimeout3
答案
答案: D
PS:这题我的理解和答案不一致,可参考,勿全信,这题知识点比较多,我自己也半懂不懂,请慎重!!!跪求大神能给出完整的解析!!!
解析:这考察的是js中的事件循环和回调队列
先汇总一下Event Loop事件循环的执行顺序:
- 1.首先执行同步任务(宏任务)
- 2.执行完所有同步任务代码之后,执行栈为空,查询是否有异步代码需要执行
- 3.执行完所有的微任务,然后会渲染页面
- 4.开始下一轮的Event Loop,执行宏任务中的异步代码,也就是setTimeout中的回调函数
宏任务:
- 1.script
- 2.setTimeout
- 3.setInterval
- 4.setImmediate
- 5.I/O
- 6.UI rendering
微任务:
- 1.process.nextTick(node 独有)
- 2.promise
- 3.MutationObserver
- 4.setImmediate
- 5.I/O
- 6.UI rendering
ok,我们首先看下题目,整理简化下
下面标记为代码行
1 async function async1(){}
2 async function async2(){}
3 console.log('script start')
4 setTimeout(0)
5 setTimeout(3)
6 setImmediate()
7 nextTick()
8 async1();
9 new Promise()
10 console.log('script end')
然后按顺序执行,首先3被调用执行,输出script start,然后执行到4,setTimeout是宏任务,会等到执行栈(调用栈)清空之后,微任务全部执行完毕之后,才会去执行,所以4会被挂起,最后执行,同样5会被挂起在4后面,下面说的 4 5 6 7 都会被挂起,然后说一下6,这个方法按照网上资料来说一般不推荐使用,但是遇到了就先看一看。
首先进入的是timers阶段,如果我们的机器性能一般,那么进入timers阶段,一毫秒已经过去了(setTimeout(fn, 0)等价于setTimeout(fn, 1)),那么setTimeout的回调会首先执行。
如果没有到一毫秒,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,此时有代码被setImmediate(),于是先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。而我们在执行代码的时候,进入timers的时间延迟其实是随机的,并不是确定的,所以会出现函数执行顺序随机的情况。
所以在我的电脑上的顺序是 3 6 4 5 ,而process.nextTick()采用的是idle观察者,它可以强势插入,在第一轮结束的时候,所以现在的顺序是 3 7 6 4 5 然后 8 9 10,根据上面挂起的情况,所以 3 8 9 10 7 6 4 5,await和.then方法开启第二轮调用,所以nextTick在 promise3前面 ,script end后面
再看看第一轮循环,也就是3 8 9 ,3没什么问题,然后看下8,首先输出1 start没问题,await 意思是等一下,等着async2()这个函数执行完毕,然后再继续,等到之后,对于await来说,分2个情况:不是promise对象、是promise对象
如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果,如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。所以就执行就先进入async2中,打印出async2,然后打印出async1 end ,因为返回的是非promise,所以先执行外面的同步代码,然后执行Promise,先打印出promise1,resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;promise调用then的前提是promise的状态为fullfilled;只有promise调用then的时候,then里面的函数才会被推入微任务中
所以按照我的理解,它的输出应该是:
script start - async1 start - async2 -async1 end-promise1 - promise2-script end -nextTick - promise3 - setImmediate -setTimeout0 - setTimeout3