陈村网站设计,网站网站做代理赚钱吗,免费推广中文黄页网,深圳市路桥建设集团有限公司招标采购网站本文精选了20多道具有一定迷惑性的js题#xff0c;主要考察的是类型判断、作用域、this指向、原型、事件循环等知识点#xff0c;每道题都配有详细傻瓜式的解析#xff0c;偏向于初学者#xff0c;大佬请随意。
第1题
let a 1
function b(a) {a 2console.log(a)
}
b(a)…本文精选了20多道具有一定迷惑性的js题主要考察的是类型判断、作用域、this指向、原型、事件循环等知识点每道题都配有详细傻瓜式的解析偏向于初学者大佬请随意。
第1题
let a 1
function b(a) {a 2console.log(a)
}
b(a)
console.log(a)答案
2、1
解析
首先基本类型数据是按值传递的所以执行b函数时b的参数a接收的值为1参数a相当于函数内部的变量当本作用域有和上层作用域同名的变量时无法访问到上层变量所以函数内无论怎么修改a都不影响上层所以函数内部打印的a是2外面打印的仍是1。
第2题
function a (b c, c 1) {console.log(b, c)
}
a()答案
报错
解析
给函数多个参数设置默认值实际上跟按顺序定义变量一样所以会存在暂时性死区的问题即前面定义的变量不能引用后面还未定义的变量而后面的可以访问前面的。
第3题
let a b 10
;(function(){ let a b 20
})()
console.log(a)
console.log(b)答案
10、20
解析
连等操作是从右向左执行的相当于b 10、let a b很明显b没有声明就直接赋值了所以会隐式创建为一个全局变量函数内的也是一样并没有声明b直接就对b赋值了因为作用域链会一层一层向上查找找了到全局的b所以全局的b就被修改为20了而函数内的a因为重新声明了所以只是局部变量不影响全局的a所以a还是10。
第4题
var a {n:1}
var b a
a.x a {n:2}
console.log(a.x)
console.log(b.x)答案
undefined、{n: 2}
解析
恕笔者不才这道题笔者做一次错一次。
反正按照网上大部分的解释是因为.运算符优先级最高所以会先执行a.x此时a、b共同指向的{n: 1}变成了{n: 1, x: undefined}然后按照连等操作从右到左执行代码a {n: 2}显然a现在指向了一个新对象然后a.x a因为a.x最开始就执行过了所以这里其实等价于({n: 1, x: undefined}).x b.x a {n: 2}。
第5题
var arr [0, 1, 2]
arr[10] 10
console.log(arr.filter(function (x) {return x undefined
}))答案
[]
解析
这题比较简单arr[10]10那么索引3到9位置上都是undefinedarr[3]等打印出来也确实是undefined但是这里其实涉及到ECMAScript版本不同对应方法行为不同的问题ES6之前的遍历方法都会跳过数组未赋值过的位置也就是空位但是ES6新增的for of方法就不会跳过。
第6题
var name World
;(function () {if (typeof name undefined) {var name Jackconsole.info(Goodbye name)} else {console.info(Hello name)}
})()答案
Goodbye Jack
解析
这道题考察的是变量提升的问题var声明变量时会把变量自动提升到当前作用域顶部所以函数内的name虽然是在if分支里声明的但是也会提升到外层因为和全局的变量name重名所以访问不到外层的name最后因为已声明未赋值的变量的值都为undefined导致if的第一个分支满足条件。
第7题
console.log(1 NaN)
console.log(1 3)
console.log(1 undefined)
console.log(1 null)
console.log(1 {})
console.log(1 [])
console.log([] {})
答案
NaN、13、NaN、1、1[object Object]、1、[object Object]
解析
这道题考察的显然是号的行为
1.如果有一个操作数是字符串那么把另一个操作数转成字符串执行连接
2.如果有一个操作数是对象那么调用对象的valueOf方法转成原始值如果没有该方法或调用后仍是非原始值则调用toString方法
3.其他情况下两个操作数都会被转成数字执行加法操作
第8题
var a{},b{key:b},c{key:c}
a[b]123
a[c]456
console.log(a[b])答案
456
解析
对象有两种方法设置和引用属性obj.name和obj[name]方括号里可以字符串、数字和变量设置是表达式等但是最终计算出来得是一个字符串对于上面的b和c它们两个都是对象所以会调用toString()方法转成字符串对象转成字符串和数组不一样和内容无关结果都是[object Obejct]所以a[b]a[c]a[[object Object]]。
第9题
var out 25
var inner {out: 20,func: function () {var out 30return this.out}
};
console.log((inner.func, inner.func)())
console.log(inner.func())
console.log((inner.func)())
console.log((inner.func inner.func)())答案
25、20、20、25
解析
这道题考察的是this指向问题
1.逗号操作符会返回表达式中的最后一个值这里为inner.func对应的函数注意是函数本身然后执行该函数该函数并不是通过对象的方法调用而是在全局环境下调用所以this指向window打印出来的当然是window下的out
2.这个显然是以对象的方法调用那么this指向该对象
3.加了个括号看起来有点迷惑人但实际上(inner.func)和inner.func是完全相等的所以还是作为对象的方法调用
4.赋值表达式和逗号表达式相似都是返回的值本身所以也相对于在全局环境下调用函数
第10题
let {a,b,c} { c:3, b:2, a:1 }
console.log(a, b, c)答案
1、2、3
解析
这题考察的是变量解构赋值的问题数组解构赋值是按位置对应的而对象只要变量与属性同名顺序随意。
第11题
console.log(Object.assign([1, 2, 3], [4, 5]))答案
[4, 5, 3]
解析
是不是从来没有用assign方法合并过数组assign方法可以用于处理数组不过会把数组视为对象比如这里会把目标数组视为是属性为0、1、2的对象所以源数组的0、1属性的值覆盖了目标对象的值。
第12题
var x1
switch(x)
{case 0: xcase 1: xcase 2: x
}
console.log(x)答案
4
解析
这题考查的是自增运算符的前缀版和后缀版以及switch的语法后缀版的自增运算符会在语句被求值后才发生所以x会仍以1的值去匹配case分支那么显然匹配到为1的分支此时x生效x变成2再执行x变成3因为没有break语句所以会进入当前case后面的分支所以再次x最终变成4。
第13题
console.log(typeof undefined typeof NULL)
console.log(typeof function () {} typeof class {})答案
true、true
解析
1.首先不要把NULL看成是nulljs的关键字是区分大小写的所以这就是一个普通的变量而且没有声明typeof对没有声明的变量使用是不会报错的返回undefinedtypeof对undefined使用也是undefined所以两者相等
2.typeof对函数使用返回functionclass只是es6新增的语法糖本质上还是函数所以两者相等
第14题
var count 0
console.log(typeof count number)
console.log(!!typeof count number)答案
true、false
解析
1.没啥好说的typeof对数字类型返回number。
2.这题考查的是运算符优先级的问题逻辑非!的优先级比全等高所以先执行!!typeof count结果为true然后执行true number结果当然为false可以点击这里查看优先级列表 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table。
第15题
use strict
a 1
var a 2
console.log(window.a)
console.log(a)答案
2、2
解析
var声明会把变量提升到当前作用域顶部所以a1并不会报错另外在全局作用域下使用var声明变量该变量会变成window的一个属性以上两点都和是否在严格模式下无关。
第16题
var i 1
function b() {console.log(i)
}
function a() {var i 2b()
}
a()答案
1
解析
这道题考察的是作用域的问题作用域其实就是一套变量的查找规则每个函数在执行时都会创建一个执行上下文其中会关联一个变量对象也就是它的作用域上面保存着该函数能访问的所有变量另外上下文中的代码在执行时还会创建一个作用域链
如果某个标识符在当前作用域中没有找到会沿着外层作用域继续查找直到最顶端的全局作用域因为js是词法作用域在写代码阶段就作用域就已经确定了换句话说是在函数定义的时候确定的而不是执行的时候所以a函数是在全局作用域中定义的虽然在b函数内调用但是它只能访问到全局的作用域而不能访问到b函数的作用域。
第17题
var obj {name: abc,fn: () {console.log(this.name)}
};
obj.name bcd
obj.fn()答案
undefined
解析
这道题考察的是this的指向问题箭头函数执行的时候上下文是不会绑定this的所以它里面的this取决于外层的this这里函数执行的时候外层是全局作用域所以this指向windowwindow对象下没有name属性所以是undefined。
第18题
const obj {a: {a: 1}
};
const obj1 {a: {b: 1}
};
console.log(Object.assign(obj, obj1))答案
{a: {b: 1}}
解析
这道题很简单因为assign方法执行的是浅拷贝所以源对象的a属性会直接覆盖目标对象的a属性。
第19题
console.log(a)
var a 1
var getNum function() {a 2
}
function getNum() {a 3
}
console.log(a)
getNum()
console.log(a)答案
undefined、1、2
解析
首先因为var声明的变量提升作用所以a变量被提升到顶部未赋值所以第一个打印出来的是undefined。
接下来是函数声明和函数表达式的区别函数声明会有提升作用在代码执行前就把函数提升到顶部在执行上下文上中生成函数定义所以第二个getNum会被最先提升到顶部然后是var声明getNum的提升但是因为getNum函数已经被声明了所以就不需要再声明一个同名变量
接下来开始执行代码执行到var getNum fun...时虽然声明被提前了但是赋值操作还是留在这里所以getNum被赋值为了一个函数下面的函数声明直接跳过最后getNum函数执行前a打印出来还是1执行后a被修改成了2所以最后打印出来的2。
第20题
var scope global scope
function a(){function b(){ console.log(scope)}return bvar scope local scope
}
a()()答案
undefined
解析
这题考查的还是变量提升和作用域的问题虽然var声明是在return语句后面但还是会提升到a函数作用域的顶部然后又因为作用域是在函数定义的时候确定的与调用位置无关所以b的上层作用域是a函数scope在b自身的作用域里没有找到向上查找找到了自动提升的并且未赋值的scope变量所以打印出undefined。
第21题
function fn (){ console.log(this)
}
var arr [fn]
arr[0]()答案
打印出arr数组本身
解析
函数作为某个对象的方法调用this指向该对象数组显然也是对象只不过我们都习惯了对象引用属性的方法obj.fn但是实际上obj[fn]引用也是可以的。
第22题
var a 1
function a(){}
console.log(a)var b
function b(){}
console.log(b)function b(){}
var b
console.log(b)答案
1、b函数本身、b函数本身
解析
这三小题都涉及到函数声明和var声明这两者都会发生提升但是函数会优先提升所以如果变量和函数同名的话变量的提升就忽略了。
1.提升完后执行到赋值代码a被赋值成了1函数因为已经声明提升了所以跳过最后打印a就是1。
2.和第一题类似只是b没有赋值操作那么执行到这两行相当于都没有操作b当然是函数。
3.和第二题类似只是先后顺序换了一下但是并不影响两者的提升顺序仍是函数优先同名的var声明提升忽略所以打印出b还是函数。
第23题
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()答案
2、4、1、1、2、3、3
解析
这是一道综合性题目首先getName函数声明会先提升然后getName函数表达式提升但是因为函数声明提升在线所以忽略函数表达式的提升然后开始执行代码执行到var getName ...时修改了getName的值赋值成了打印4的新函数。
1.执行Foo函数的静态方法打印出2。
2.执行getName当前getName是打印出4的那个函数。
3.执行Foo函数修改了全局变量getName赋值成了打印1的函数然后返回this因为是在全局环境下执行所以this指向window因为getName已经被修改了所以打印出1。
4.因为getName没有被重新赋值所以再执行仍然打印出1。
5.new操作符是用来调用函数的所以new Foo.getName()相当于new (Foo.getName)()所以new的是Foo的静态方法getName打印出2。
6.因为点运算符.的优先级和new是一样高的所以从左往右执行相当于(new Foo()).getName()对Foo使用new调用会返回一个新创建的对象然后执行该对象的getName方法该对象本身并没有该方法所以会从Foo的原型对象上查找找到了所以打印出3。
7.和上题一样点运算符.的优先级和new一样高另外new是用来调用函数的所以new new Foo().getName()相当于new ((new Foo()).getName)()括号里面的就是上一题所以最后找到的是Foo原型上的方法无论是直接调用还是通过new调用都会执行该方法所以打印出3。
第24题
const person {address: {country:china,city:hangzhou},say: function () {console.log(its ${this.name}, from ${this.address.country})},setCountry:function (country) {this.address.countrycountry}
}const p1 Object.create(person)
const p2 Object.create(person)p1.name Matthew
p1.setCountry(American)p2.name Bob
p2.setCountry(England)p1.say()
p2.say()答案
its Matthew, from England
its Bob, from England
解析
Object.create方法会创建一个对象并且将该对象的__proto__属性指向传入的对象所以p1和p2两个对象的原型对象指向了同一个对象接着给p1添加了一个name属性然后调用了p1的setCountry方法p1本身是没有这个方法的所以会沿着原型链进行查找在它的原型上也就是person对象上找到了这个方法执行这个方法会给address对象的country属性设置传入的值p1本身也是没有address属性的
但是和name属性不一样address属性在原型对象上找到了并且因为是个引用值所以会成功修改它的country属性接着对p2的操作也是一样然后因为原型中存在引用值会在所有实例中共享所以p1和p2它们引用的address也是同一个对象一个实例修改了会反映到所有实例上所以p2的修改会覆盖p1的修改最终country的值为England。
第25题
setTimeout(function() {console.log(1)
}, 0)
new Promise(function(resolve) {console.log(2)for( var i0 ; i10000 ; i ) {i 9999 resolve()}console.log(3)
}).then(function() {console.log(4)
})
console.log(5)答案
2、3、5、4、1
解析
这道题显然考察的是事件循环的知识点。
js是一门单线程的语言但是为了执行一些异步任务时不阻塞代码以及避免等待期间的资源浪费js存在事件循环的机制单线程指的是执行js的线程称作主线程其他还有一些比如网络请求的线程、定时器的线程主线程在运行时会产生执行栈栈中的代码如果调用了异步api的话则会把事件添加到事件队列里
只要该异步任务有了结果便会把对应的回调放到【任务队列】里当执行栈中的代码执行完毕后会去读取任务队列里的任务放到主线程执行当执行栈空了又会去检查如此往复也就是所谓的事件循环。
异步任务又分为【宏任务】比如setTimeout、setInterval和【微任务】比如promise它们分别会进入不同的队列执行栈为空完后会优先检查微任务队列如果有微任务的话会一次性执行完所有的微任务然后去宏任务队列里检查如果有则取出一个任务到主线程执行执行完后又会去检查微任务队列如此循环。
回到这题首先整体代码作为一个宏任务开始执行遇到setTimeout相应回调会进入宏任务队列然后是promisepromise的回调是同步代码所以会打印出2for循环结束后调用了resolve所以then的回调会被放入微任务队列然后打印出3最后打印出5到这里当前的执行栈就空了那么先检查微任务队列发现有一个任务那么取出来放到主线程执行打印出4最后检查宏任务队列把定时器的回调放入主线程执行打印出1。
第26题
console.log(1);setTimeout(function() {console.log(2);process.nextTick(function() {console.log(3);});new Promise(function(resolve) {console.log(4);resolve();}).then(function() {console.log(5);});
}); process.nextTick(function() {console.log(6);
});new Promise(function(resolve) {console.log(7);resolve();
}).then(function() {console.log(8);
});setTimeout(function() {console.log(9);process.nextTick(function() {console.log(10);}) new Promise(function(resolve) {console.log(11);resolve();}).then(function() {console.log(12)});
})答案
1、7、6、8、2、4、9、11、3、10、5、12
解析
这道题和上一题差不多但是出现了process.nextTick所以显然是在node环境下node也存在事件循环的概念但是和浏览器的有点不一样nodejs中的宏任务被分成了几种不同的阶段
两个定时器属于timers阶段setImmediate属于check阶段socket的关闭事件属于close callbacks阶段其他所有的宏任务都属于poll阶段
除此之外只要执行到前面说的某个阶段那么会执行完该阶段所有的任务这一点和浏览器不一样浏览器是每次取一个宏任务出来执行执行完后就跑去检查微任务队列了
但是nodejs是来都来了一次全部执行完该阶段的任务好了那么process.nextTick和微任务在什么阶段执行呢在前面说的每个阶段的后面都会执行但是process.nextTick会优先于微任务一图胜千言 理解了以后再来分析这道题就很简单了首先执行整体代码先打印出1setTimeout回调扔进timers队列nextTick的扔进nextTick的队列promise的回调是同步代码执行后打印出7then回调扔进微任务队列然后又是一个setTimeout回调扔进timers队列到这里当前节点就结束了
检查nextTick和微任务队列nextTick队列有任务执行后打印出6微任务队列也有打印出8接下来按顺序检查各个阶段check队列、close callbacks队列都没有任务到了timers阶段发现有两个任务先执行第一个打印出2然后nextTick的扔进nextTick的队列执行promise打印出4then回调扔进微任务队列
再执行第二个setTimeout的回调打印出9然后和刚才一样nextTick的扔进nextTick的队列执行promise打印出11then回调扔进微任务队列到这里timers阶段也结束了执行nextTick队列的任务发现又两个任务依次执行打印出3和10然后检查微任务队列也是两个任务依次执行打印出5和12到这里是有队列都清空了。 作者街角小林 https://juejin.cn/post/6989433079760683022