每日20道面试题带解析02

来源 | https://www.cnblogs.com/echoyya/p/14550258.html
1、写出执行结果,并解释原因
var a = [0];if ([0]) {console.log(a == true);} else {console.log("wut");}// 输出什么?
答案 : false解析 : if(condition)判断时,会把condition转换成boolean然后做判断,[0]是一个有值的list,所以转成boolean是true,而A == B的比较时,如果A和B的类型不一样,会先把A和B转化成相同的type,通常转为number//分成以下步骤//把true转化成number,true变成1[0] == 1;//list是object//先看[0].valueOf(),结果还是[0]//再看[0].toString(),结果是“0” type是string"0" == 1;//把“0” string转化成number,“0”变成0,0不等于10 == 1; //结果是false
2、写出执行结果,并解释原因
[] //解析成什么?答案及解析
答案 : [true,true]解析 : 运算符优先级,分步解析1 < 2 < 3 => true < 3 => 1 < 3 => true3 < 2 < 1 => false < 1 => 0 < 1 => true
3、写出执行结果,并解释原因
function foo() { }var oldName = foo.name;foo.name = "bar";[oldName, foo.name]
答案及解析
答案 : ['foo','foo']解析 : 函数的name是只读属性不可修改
4、写出执行结果,并解释原因
var lowerCaseOnly = /^[a-z]+$/;[lowerCaseOnly.test(null), lowerCaseOnly.test()]
答案及解析
答案 : [true, true]解析 : 正则容易忽视的坑,test在检测时会隐性将内容转为字符串,其实等同于:[lowerCaseOnly.test('null'), lowerCaseOnly.test('undefined')]
5、写出执行结果,并解释原因
if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {console.log('a gif file')} else {console.log('not a gif file')}
答案及解析
答案 : 'a gif file'解析 : 正则的隐式转换,match方法第一个参数接收一个正则表达式或者一个字符串,但如果是字符串会隐式转为正则,所以上述代码等同于:'http://giftwrapped.com/picture.jpg'.match(/.gif/)而在正则中 点 . 表示通配符,所以成功匹配到/gif,匹配成功,输出a gif file。
6、写出执行结果,并解释原因
function user(obj) {obj.name = "北京"obj = new Object()obj.name = "上海"}let person = new Object();user(person);console.log(person.name);
答案及解析
答案 : 北京解析 : 对象作为参数,传递进去的是这个对象的地址,1. obj.name是给person这个对象赋值;2. obj = new Object(),把obj指向另一个对象,3. obj.name现在是给这个新对象赋值,不影响person这个变量指向的对象;4. 两个obj指向的对象的引用地址不同。所有函数的参数都是按值传递的。5. 基本类型的传递同基本类型变量的赋值一样,按值传递,在函数体内修改参数的值,不会影响到函数外部。6. 引用类型的值传递同引用类型变量的赋值一样,按引用传递,传入函数的是原始值的地址,因此在函数内部修改参数,将会影响到原始值。
7、写出执行结果,并解释原因
let x, y;try {throw new Error();}catch (x) {x = 1;y = 2;var a = 3console.log(a); // ?console.log(x); // ?}console.log(a); // ?console.log(x); // ?console.log(y); // ?
答案及解析
答案 : 3 1 3 undefined 2解析 :1. catch的作用域,其实并不是常见的块级作用域,且不能绑定自己的内部声明的变量(如a)。2. catch创建的块作用域,只对catch的参数x有效。3. 对于在内部声明的变量,catch并没有创建一个新的作用域,只是一个普通的代码块。因此块外仍可访问
8、写出执行结果,并解释原因
function fn() {getValue = function () { console.log(1); };return this;}fn.getValue = function () { console.log(2);};fn.prototype.getValue = function () {console.log(3);};var getValue = function () {console.log(4);};function getValue() {console.log(5);}getValue(); // ?fn().getValue(); // ?getValue(); // ?new fn.getValue(); // ?new fn().getValue(); // ?
答案及解析
答案 : 4 1 1 2 3考察 : 变量定义提升、this指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级解析 : 为强调重点内容,在下方使用标记语言描述。
第一问 getValue():
直接调用,关注点在4,5上:
JS存在一种变量声明被提升的机制,函数声明会被提升到作用域的最前面,即使写在最后,也还是会被提升至最前面。
函数表达式和函数声明的区别,函数声明解析时会提升,函数表达式的值是在JS运行时确定,并且在表达式赋值完成后,该函数才能调用
函数声明5被函数表达式4覆盖, 输出4
执行fn函数,调用fn函数返回值对象的 getValue 属性函数;
此时 getValue 函数没有用var进行声明,已将外层作用域的getValue函数修改;
fn函数返回this,此时函数执行确定this指向window对象,相当于执行window.getValue(),而getValue已经被修改成console.log(1), 输出1
执行完第6步,getValue函数已被修改,console.log(1), 输出1
考察JS的运算符优先级问题,
点的优先级高于new无参数列表,相当于new (fn.getValue())
当点运算完后有个括号(),此时就是变成new有参数列表,优先级高于函数执行,所以直接执行new。这也是为什么遇到()不先函数调用再new。
最终相当于将 getValue函数,作为构造函数来执行, 输出2
这里带括号是new 有参数列表,new有参数列表的优先级与点的优先级相同,按从左到右的顺序执行。
先执行有参数列表,再执行点的优先级,最后再函数调用
fn作为构造函数有返回值,在JS中构造函数的返回值可有可无
没有返回值:返回实例化的对象
有返回值:检查其返回值是否为引用类型
非引用类型:基本类型则与无返回值相同,实际返回其实例化对象。
引用类型:实际返回值为这个引用类型
fn 函数返回的是this,this在构造函数中本来就代表当前实例化对象, 最终fn返回实例化对象。调用对象的getValue方法,而构造函数中没有getValue,调用原型对象(prototype)上的getValue函数。输出3。
9、写出执行结果,并解释原因
let length = 10;function fn() {console.log(this.length);}var obj = {length: 5,method: function (fn) {fn();arguments[0]();}};obj.method(fn, 1);
答案及解析
答案 : 0 2解析 : 为强调重点内容,在下方使用标记语言描述。
第一问:fn()
任意函数里嵌套非箭头函数,嵌套函数内this 在未指定的情况下,指向的是 window 对象,这里执行 fn会打印window.length,
let声明的变量有形成块级作用域,且不存在声明提升,length属性并没有挂载到window对象中。(test:let a = 1; window.a // undefined)
此时打印的便是window自带的length属性,表示iframe个数,默认为0。输出0。
arguments类数组是函数参数的引用, arguments[0]指向 fn,
arguments[0]() 是作为 arguments对象的属性[0]来调用 fn的,谁调用 this 就指向谁;所以 fn 中的 this 指向arguments(对象的属性调用方法,this指向该对象)
arguments有两个参数,fn和1,因此argumengts.length = 2 ,输出2。
[function fn(){console.log(this.length)}][0](); // 1 数组也是对象,调用数组对象的0属性,函数作为数组对象的属性调用,函数中的this 当然指向这个数组,所以返回数组的length
10、写出执行结果,并解释原因
var a=10;var foo={a:20,bar:function(){var a=30;return this.a;}}console.log(foo.bar()); // ?console.log((foo.bar)()); // ?console.log((foo.bar=foo.bar)()); // ?console.log((foo.bar,foo.bar)()); // ?
答案及解析
答案 : 20 20 10 10解析 : 为强调重点内容,在下方使用标记语言描述。
第一问 foo.bar()
foo调用,this指向foo , 输出20。
表达式加了括号,括号的作用是改变表达式的运算顺序,而在这加与不加并无影响,相当于foo.bar(), 输出20。
等号运算相当于重新给foo.bar定义,相当于一个匿名函数赋值给一个全局变量,foo.bar是在window作用域下,this指代的是window,输出10。
foo.bar = function () {var a = 10;return this.a;}
第四问 (foo.bar,foo.bar)()
逗号运算符求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值,最后整个逗号运算符的返回值是最后一个表达式的值。
经过逗号运算符后,就是纯函数,不再是对象方法的引用,所以this指向window,输出10。
技巧:经过赋值,运算符运算后,都是纯函数,不是对象方法的引用。函数中this指向都是windows。
11、写出执行结果,并解释原因
function getName(){return{name:'Echoyya'}}console.log(getName()); // ?
答案及解析
答案 : undefined解析 : 如果continue、break、return、throw 这四个语句后面,直接跟换行符,则会自动添加分号
12、写出执行结果,并解释原因
const num = parseInt("2*4",10);console.log(num); // ?
答案及解析
答案 : 2解析 : parseInt会检查字符串中的字符是否合法. 一旦遇到一个在指定进制(第二个参数)中不合法的字符后,立即停止解析并且忽略后面所有的字符。*为非法数字。所以只解析到 2,并将其解析为十进制的2. 值即为 2
13、写出执行结果,并解释原因
var x = 20;var temp = {x: 40,foo: function () {var x = 10;console.log(this.x);}};(temp.foo, temp.foo)(); // ?
答案及解析
答案 : 20技巧 : 经过赋值,运算符运算后,都是纯函数,不是对象方法的引用。函数中this指向都是windows。解析 : 逗号操作符会从左到右计算它的操作数,返回最后一个操作数的值。所以(temp.foo, temp.foo)();等价于var fun = temp.foo;fun();fun调用时this指向window,因此返回 20。
14、写出执行结果,并解释原因
const company = { name: "Echoyya" };Object.defineProperty(company, "address", { value: "北京" });console.log(company); // ?console.log(Object.keys(company)); // ?
答案及解析
答案 : {name:"Echoyya",address:"北京"}, ["name"]解析 : defineProperty方法可以给对象添加一个新属性,或者修改已经存在的属性。而使用defineProperty给对象添加属性之后,属性默认为不可枚举,Object.keys方法仅返回对象中可枚举的属性,因此只打印name
15、写出执行结果,并解释原因
let num = 10;const inNum = () => num++;const inPaNum = number => number++;const num1 = inNum();const num2 = inPaNum(num1);console.log(num1); // ?console.log(num2); // ?
答案及解析
答案 : 10 10解析 : 一元操作符 ++ 先返回操作值, 再执行自增操作值。1. num1 是10,因为 inNum 函数先返回 num 的值,在执行 num 自增2. num2 是10,因为 num1 作为参数传入 inPaNum,同理函数先返回 number 的值,在执行 number 自增
16、写出执行结果,并解释原因
const value = { number: 10 };const multiply = (x = { ...value }) => {console.log(x.number *= 2);};multiply(); // ?multiply(); // ?multiply(value); // ?multiply(value); // ?
答案及解析
答案 : 20 20 20 40解析 :1. ES6中可以使用参数默认值, 函数未传参,或参数为undefined,将使用参数默认值。2. 解构 value 对象并赋值给一个新对象,因此 x 的默认值为 {number:10} 。3. 默认参数在调用时才会计算,每次调用函数,都会创建一个新的对象。调用 multiply(),x的默认值都为 {number:10},因此输出 204. 调用 multiply(value),实际上修改了 value.number的值,输出 205. 再次调用调用 multiply(value),value.number之前被修改为 20,因此输出 40。
17、写出执行结果,并解释原因
// index.jsconsole.log('running index.js');import { sum } from './sum.js';console.log(sum(1, 2));// sum.jsconsole.log('running sum.js');export const sum = (a, b) => a + b;
答案及解析
答案 : running sum.js, running index.js, 3解析 : import命令是编译阶段执行的,在运行之前。因此被导入的模块会先运行,而导入模块的文件会后执行。这是CommonJS 中 require() 和 import之间的区别。require()可以在运行代码时按需加载。如果使用 require,那么running index.js、running sum.js、 3会被依次打印。
18、写出执行结果,并解释原因
function addToList(item, list) {return list.push(item);}const result = addToList("company", ["yideng"]);console.log(result); // ?
答案及解析
答案 : 2解析 : push()方法返回新数组的长度。若想返回新数组,应该在push之后返回list。
19、实现(5).add(3).minus(2) 功能
// 实现 (5).add(3).minus(2) 功能console.log((5).add(3).minus(2)); // 6
答案及解析
答案 :Number.prototype.add = function (number) {if (typeof number !== 'number') {throw new Error('请输入数字~');}return this + number;};Number.prototype.minus = function (number) {if (typeof number !== 'number') {throw new Error('请输入数字~');}return this - number;};console.log((5).add(3).minus(2)); // 6
20、不使用模运算符的情况下,检查一个数是否是偶数
isEven(num) // true Or false答案及解析
答案 :1)递归方式function isEven(num){const number = Math.abs(num); // 取绝对值if(number === 1) return false;if(number == 0 ) return true;return isEven(number -2);}-------------------------------------------------2)通过Math.round,利用奇数除以2会有小数的特点function isEven(num){return parseInt(num/2) === Math.round(num/2);}
本文完~
推荐阅读
学习更多技能
请点击下方公众号
![]()

