每日20道面试题带解析 — (21 - 40)
答案在问题下方的折叠部分,点击即可展开问题。祝你好运 ❤️
以下题目,本人验证无误,查阅了相关资料,得出解析部分并做了相关总结,希望对正准备跳槽或找工作的你有帮助!
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不等于1
0 == 1; //结果是false
2. 写出执行结果,并解释原因
[1 < 2 < 3, 3 < 2 < 1] //解析成什么?
点此查看答案及解析
答案 : [true,true]
解析 : 运算符优先级,分步解析
1 < 2 < 3 => true < 3 => 1 < 3 => true
3 < 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 = 3
console.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().getValue():
- 执行fn函数,调用fn函数返回值对象的 getValue 属性函数;
- 此时 getValue 函数没有用var进行声明,已将外层作用域的getValue函数修改;
- fn函数返回this,此时函数执行确定this指向window对象,相当于执行window.getValue(),而getValue已经被修改成console.log(1), 输出 1
第三问 getValue():
- 执行完第6步,getValue函数已被修改,console.log(1), 输出 1
第四问 new fn.getValue():
- 考察JS的
运算符优先级
问题, 点的优先级高于new无参数列表
,相当于new (fn.getValue())- 当点运算完后有个括号(),此时就是变成
new有参数列表,优先级高于函数执行
,所以直接执行new。这也是为什么遇到()不先函数调用再new。 - 最终相当于将 getValue函数,作为构造函数来执行, 输出 2
第五问 new fn().getValue()
- 这里带括号是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[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
解析 : 为强调重点内容,在下方使用标记语言描述。
本题主要考察this指向问题,推荐博文:
一文搞懂 this、apply、call、bind: https://www.cnblogs.com/echoyya/p/14506269.html
JS五种绑定彻底弄懂this: https://www.cnblogs.com/echoyya/p/14506742.html
解析
第一问 foo.bar()
- foo调用,this指向foo , 输出 20
第二问 (foo.bar)()
- 表达式加了括号,
括号的作用是改变表达式的运算顺序
,而在这加与不加并无影响,相当于foo.bar(), 输出 20
第三问 (foo.bar=foo.bar)()
-
等号运算相当于重新给foo.bar定义,相当于
一个匿名函数赋值给一个全局变量
,foo.bar是在window作用域下,this指代的是window,输出 10foo.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},因此输出 20
4. 调用 multiply(value),实际上修改了 value.number的值,输出 20
5. 再次调用调用 multiply(value),value.number之前被修改为 20,因此输出 40。
17. 写出执行结果,并解释原因
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));
// sum.js
console.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);
}