每日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():

  1. 直接调用,关注点在4,5上:
  2. JS存在一种变量声明被提升的机制,函数声明会被提升到作用域的最前面,即使写在最后,也还是会被提升至最前面。
  3. 函数表达式和函数声明的区别,函数声明解析时会提升,函数表达式的值是在JS运行时确定,并且在表达式赋值完成后,该函数才能调用
  4. 函数声明5被函数表达式4覆盖输出 4

第二问 fn().getValue():

  1. 执行fn函数,调用fn函数返回值对象的 getValue 属性函数
  2. 此时 getValue 函数没有用var进行声明,已将外层作用域的getValue函数修改;
  3. fn函数返回this,此时函数执行确定this指向window对象,相当于执行window.getValue(),而getValue已经被修改成console.log(1), 输出 1

第三问 getValue():

  1. 执行完第6步,getValue函数已被修改,console.log(1), 输出 1

第四问 new fn.getValue():

  1. 考察JS的运算符优先级问题,
  2. 点的优先级高于new无参数列表,相当于new (fn.getValue())
  3. 当点运算完后有个括号(),此时就是变成new有参数列表,优先级高于函数执行,所以直接执行new。这也是为什么遇到()不先函数调用再new。
  4. 最终相当于将 getValue函数,作为构造函数来执行, 输出 2

第五问 new fn().getValue()

  1. 这里带括号是new 有参数列表,new有参数列表的优先级与点的优先级相同,按从左到右的顺序执行。
  2. 先执行有参数列表,再执行点的优先级,最后再函数调用
  3. fn作为构造函数有返回值,在JS中构造函数的返回值可有可无
    • 没有返回值:返回实例化的对象
    • 有返回值:检查其返回值是否为引用类型。
      • 非引用类型:基本类型则与无返回值相同,实际返回其实例化对象。
      • 引用类型:实际返回值为这个引用类型
  4. 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()

  1. 任意函数里嵌套非箭头函数,嵌套函数内this 在未指定的情况下,指向的是 window 对象,这里执行 fn会打印window.length,
  2. let声明的变量有形成块级作用域,且不存在声明提升,length属性并没有挂载到window对象中。(test:let a = 1; window.a // undefined)
  3. 此时打印的便是window自带的length属性,表示iframe个数,默认为0。输出 0

第二问:arguments[0]()

  1. arguments类数组是函数参数的引用, arguments[0]指向 fn,
  2. arguments[0]() 是作为 arguments对象的属性[0]来调用 fn的,谁调用 this 就指向谁;所以 fn 中的 this 指向arguments(对象的属性调用方法,this指向该对象)
  3. arguments有两个参数,fn和1,因此argumengts.length = 2 输出 2

扩展

  1. [function fn(){console.log(this.length)}][0](); // 1
  2. 数组也是对象,调用数组对象的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()

  1. foo调用,this指向foo , 输出 20

第二问 (foo.bar)()

  1. 表达式加了括号,括号的作用是改变表达式的运算顺序,而在这加与不加并无影响,相当于foo.bar(), 输出 20

第三问 (foo.bar=foo.bar)()

  1. 等号运算相当于重新给foo.bar定义,相当于一个匿名函数赋值给一个全局变量,foo.bar是在window作用域下,this指代的是window,输出 10

    foo.bar = function () {
    	var a = 10;
    	return this.a;
    }
    

第四问 (foo.bar,foo.bar)()

  1. 逗号运算符求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值,最后整个逗号运算符的返回值是最后一个表达式的值。
  2. 经过逗号运算符后,就是纯函数,不再是对象方法的引用,所以this指向window,输出 10
  3. 技巧:经过赋值,运算符运算后,都是纯函数,不是对象方法的引用。函数中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);
  }

posted @ 2021-03-17 16:48  Echoyya、  阅读(196)  评论(0编辑  收藏  举报