Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你有必要知道的 25 个 JavaScript 面试题 #17

Open
dwqs opened this issue Aug 3, 2016 · 38 comments
Open

你有必要知道的 25 个 JavaScript 面试题 #17

dwqs opened this issue Aug 3, 2016 · 38 comments

Comments

@dwqs
Copy link
Owner

dwqs commented Aug 3, 2016

1、使用 typeof bar === "object" 判断 bar 是不是一个对象有神马潜在的弊端?如何避免这种弊端?

使用 typeof 的弊端是显而易见的(这种弊端同使用 instanceof):

let obj = {};
let arr = [];

console.log(typeof obj === 'object');  //true
console.log(typeof arr === 'object');  //true
console.log(typeof null === 'object');  //true

从上面的输出结果可知,typeof bar === "object" 并不能准确判断 bar 就是一个 Object。可以通过 Object.prototype.toString.call(bar) === "[object Object]" 来避免这种弊端:

let obj = {};
let arr = [];

console.log(Object.prototype.toString.call(obj));  //[object Object]
console.log(Object.prototype.toString.call(arr));  //[object Array]
console.log(Object.prototype.toString.call(null));  //[object Null]

[] === false 是返回 false 的。

2、下面的代码会在 console 输出神马?为什么?

(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));   
console.log("b defined? " + (typeof b !== 'undefined'));

这跟变量作用域有关,输出换成下面的:

console.log(b); //3
console,log(typeof a); //undefined

拆解一下自执行函数中的变量赋值:

b = 3;
var a = b;

所以 b 成了全局变量,而 a 是自执行函数的一个局部变量。

3、下面的代码会在 console 输出神马?为什么?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

第一个和第二个的输出不难判断,在 ES6 之前,JavaScript 只有函数作用域,所以 func 中的 IIFE 有自己的独立作用域,并且它能访问到外部作用域中的 self,所以第三个输出会报错,因为 this 在可访问到的作用域内是 undefined,第四个输出是 bar。如果你知道闭包,也很容易解决的:

(function(test) {
            console.log("inner func:  this.foo = " + test.foo);  //'bar'
            console.log("inner func:  self.foo = " + self.foo);
}(self));

4、将 JavaScript 代码包含在一个函数块中有神马意思呢?为什么要这么做?

换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。

IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。

for(var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

上面的输出并不是你以为的0,1,2,3,4,而输出的全部是5,这时 IIFE 就能有用了:

for(var i = 0; i < 5; i++) {
    (function(i) {
      setTimeout(function() {
        console.log(i);  
      }, 1000);
    })(i)
}

而在 JQuery/Node 的插件和模块开发中,为避免变量污染,也是一个大大的 IIFE:

(function($) { 
        //代码
 } )(jQuery);

5、在严格模式('use strict')下进行 JavaScript 开发有神马好处?

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

6、下面两个函数的返回值是一样的吗?为什么?

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}

在编程语言中,基本都是使用分号(;)将语句分隔开,这可以增加代码的可读性和整洁性。而在JS中,如若语句各占独立一行,通常可以省略语句间的分号(;),JS 解析器会根据能否正常编译来决定是否自动填充分号:

var test = 1 + 
2
console.log(test);  //3

在上述情况下,为了正确解析代码,就不会自动填充分号了,但是对于 returnbreakcontinue 等语句,如果后面紧跟换行,解析器一定会自动在后面填充分号(;),所以上面的第二个函数就变成了这样:

function foo2()
{
  return;
  {
      bar: "hello"
  };
}

所以第二个函数是返回 undefined

7、神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?

NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN

console.log(isNaN(NaN)); //true
console.log(isNaN(23)); //false
console.log(isNaN('ds')); //true
console.log(isNaN('32131sdasd')); //true
console.log(NaN === NaN); //false
console.log(NaN === undefined); //false
console.log(undefined === undefined); //false
console.log(typeof NaN); //number
console.log(Object.prototype.toString.call(NaN)); //[object Number]

ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN().

8、解释一下下面代码的输出

console.log(0.1 + 0.2);   //0.30000000000000004
console.log(0.1 + 0.2 == 0.3);  //false

JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。

由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:

0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)

双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100...因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。

对于保证浮点数计算的正确性,有两种常见方式。

一是先升幂再降幂:

function add(num1, num2){
  let r1, r2, m;
  r1 = (''+num1).split('.')[1].length;
  r2 = (''+num2).split('.')[1].length;

  m = Math.pow(10,Math.max(r1,r2));
  return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2));   //0.3
console.log(add(0.15,0.2256)); //0.3756

二是是使用内置的 toPrecision()toFixed() 方法,注意,方法的返回值字符串。

function add(x, y) {
    return x.toPrecision() + y.toPrecision()
}
console.log(add(0.1,0.2));  //"0.10.2"

9、实现函数 isInteger(x) 来判断 x 是否是整数

可以将 x 转换成10进制,判断和本身是不是相等即可:

function isInteger(x) { 
    return parseInt(x, 10) === x; 
}

ES6 对数值进行了扩展,提供了静态方法 isInteger() 来判断参数是否是整数:

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false

JavaScript能够准确表示的整数范围在 -2^532^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。

10、在下面的代码中,数字 1-4 会以什么顺序输出?为什么会这样输出?

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();

这个就不多解释了,主要是 JavaScript 的定时机制和时间循环,不要忘了,JavaScript 是单线程的。详解可以参考 从setTimeout谈JavaScript运行机制

11、写一个少于 80 字符的函数,判断一个字符串是不是回文字符串

function isPalindrome(str) {
    str = str.replace(/\W/g, '').toLowerCase();
    return (str == str.split('').reverse().join(''));
}

这个题我在 codewars 上碰到过,并收录了一些不错的解决方式,可以戳这里:Palindrome For Your Dome

12、写一个按照下面方式调用都能正常工作的 sum 方法

console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

针对这个题,可以判断参数个数来实现:

function sum() {
  var fir = arguments[0];
  if(arguments.length === 2) {
    return arguments[0] + arguments[1]
  } else {
    return function(sec) {
       return fir + sec;
    }
  }

}

13、根据下面的代码片段回答后面的问题

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

1、点击 Button 4,会在控制台输出什么?
2、给出一种符合预期的实现方式

1、点击5个按钮中的任意一个,都是输出5
2、参考 IIFE。

14、下面的代码会输出什么?为什么?

var arr1 = "john".split(''); j o h n
var arr2 = arr1.reverse(); n h o j
var arr3 = "jones".split(''); j o n e s
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

会输出什么呢?你运行下就知道了,可能会在你的意料之外。

MDN 上对于 reverse() 的描述是酱紫的:

Description
The reverse method transposes the elements of the calling array object in place, mutating the array, and returning a reference to the array.

reverse() 会改变数组本身,并返回原数组的引用。

slice 的用法请参考:slice

15、下面的代码会输出什么?为什么?

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

输出什么,自己去运行吧,需要注意三个点:

  • 多个数字和数字字符串混合运算时,跟操作数的位置有关
console.log(2 + 1 + '3'); / /‘33’
console.log('3' + 2 + 1); //'321'
  • 数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字
console.log(typeof '3');   // string
console.log(typeof +'3');  //number

同样,可以在数字前添加 '',将数字转为字符串

console.log(typeof 3);   // number
console.log(typeof (''+3));  //string
  • 对于运算结果不能转换成数字的,将返回 NaN
console.log('a' * 'sd');   //NaN
console.log('A' - 'B');  // NaN

这张图是运算转换的规则

运算符转换

16、如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

原文上的解决方式是加个定时器:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

解决方式的原理请参考第10题。

17、什么是闭包?举例说明

可以参考此篇:从作用域链谈闭包

18、下面的代码会输出什么?为啥?

for (var i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

请往前面翻,参考第4题,解决方式已经在上面了

19、解释下列代码的输出

console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

逻辑与和逻辑或运算符会返回一个值,并且二者都是短路运算符:

  • 逻辑与返回第一个是 false 的操作数 或者 最后一个是 true的操作数
console.log(1 && 2 && 0);  //0
console.log(1 && 0 && 1);  //0
console.log(1 && 2 && 3);  //3

如果某个操作数为 false,则该操作数之后的操作数都不会被计算

  • 逻辑或返回第一个是 true 的操作数 或者 最后一个是 false的操作数
console.log(1 || 2 || 0); //1
console.log(0 || 2 || 1); //2
console.log(0 || 0 || false); //false

如果某个操作数为 true,则该操作数之后的操作数都不会被计算

如果逻辑与和逻辑或作混合运算,则逻辑与的优先级高:

console.log(1 && 2 || 0); //2
console.log(0 || 2 && 1); //1
console.log(0 && 2 || 1); //1

在 JavaScript,常见的 false 值:

0, '0', +0, -0, false, '',null,undefined,null,NaN

要注意空数组([])和空对象({}):

console.log([] == false) //true
console.log({} == false) //false
console.log(Boolean([])) //true
console.log(Boolean({})) //true

所以在 if 中,[]{} 都表现为 true

if

20、解释下面代码的输出

console.log(false == '0')
console.log(false === '0')

请参考前面第14题运算符转换规则的图。

21、解释下面代码的输出

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

输出是 456,参考原文的解释:

The reason for this is as follows: When setting an object property, JavaScript will implicitly stringify the parameter value. In this case, since b and c are both objects, they will both be converted to "[object Object]". As a result, a[b] anda[c] are both equivalent to a["[object Object]"] and can be used interchangeably. Therefore, setting or referencing a[c] is precisely the same as setting or referencing a[b].

22、解释下面代码的输出

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

结果是10的阶乘。这是一个递归调用,为了简化,我初始化 n=5,则调用链和返回链如下:

递归

23、解释下面代码的输出

(function(x) {
    return (function(y) {
        console.log(x);
    })(2)
})(1);

输出1,闭包能够访问外部作用域的变量或参数。

24、解释下面代码的输出,并修复存在的问题

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

getSecretIdentity 赋给 stoleSecretIdentity,等价于定义了 stoleSecretIdentity 函数:

var stoleSecretIdentity =  function (){
        return this._name;
}

stoleSecretIdentity 的上下文是全局环境,所以第一个输出 undefined。若要输出 John Doe,则要通过 callapplybind 等方式改变 stoleSecretIdentitythis 指向(hero)。

第二个是调用对象的方法,输出 John Doe

25、给你一个 DOM 元素,创建一个能访问该元素所有子元素的函数,并且要将每个子元素传递给指定的回调函数。

函数接受两个参数:

  • DOM
  • 指定的回调函数

原文利用 深度优先搜索(Depth-First-Search) 给了一个实现:

function Traverse(p_element,p_callback) {
   p_callback(p_element);
   var list = p_element.children;
   for (var i = 0; i < list.length; i++) {
       Traverse(list[i],p_callback);  // recursive call
   }
}

文章参考:

25 Essential JavaScript Interview Questions
JavaScript中的立即执行函数表达式
Javascript 严格模式详解

声明:文中对问题的回答仅代表博主个人观点

@jerrybendy
Copy link

关于第 5 题严格模式下的好处我看到过另外一个版本大致是说:使用严格模式会增加代码检查的步骤并降低代码的执行效率,通常情况下在开发时使用严格模式,而在发布到生产环境时去掉严格模式的定义。与文中所述的“提高编译器效率,增加运行速度”完全相反。不知道哪个版本才是正确可行的,望指教,谢谢。

@bdbai
Copy link

bdbai commented Feb 21, 2017

还漏掉了一些 ES6 特性,比如 12 题可以用剩余参数实现,避免访问性能不佳的 arguments。

@zhuzhuaicoding
Copy link

mark

@jiangtianyou
Copy link

reverse() 会改变数组本身,并返回原数组的引用。不是返回原数组吧,返回的是颠倒后的数组

@jasonleft
Copy link

赞!

@beyond5959
Copy link

第一条,用 obj.constructor 和 arr.constructor 也可以区分,不过 null 不行。

@honwenle
Copy link

第19条,'0'不是false。另外,如果能顺便解释一下[]==![]就更好了

@jerrybendy
Copy link

@honwenle '0' == false 确实是成立的,因为在比较的过程中会把两者都转换为整数 0,所以对比就变成了 0 == 0 ,自然是成立的。

第二个问题可以直接参考这篇文章:https://www.h5jun.com/post/why-false-why-true.html

@honwenle
Copy link

@jerrybendy 谢谢耐心解答,对于==我看过很多资料,还是不能完全理解,不过尽量都用===就好了

@xiguaaxigua
Copy link

console.log(undefined === undefined); // true

@findsomeoneyys
Copy link

谢谢~作为初学者学到了很多!

@Mageenz
Copy link

Mageenz commented Mar 1, 2017

这些题目在笔试的时候应该会出现

@mqliutie
Copy link

mqliutie commented Mar 6, 2017

只有我觉得这些东西出在面试题中没什么必要么....

@YiBanCangBai
Copy link

老铁说的有道理👏

@lazyhero
Copy link

赞 虽然工作中 一查就到 但是…… 面试考需要瞬间答上来 所以……还是看吧 明白其中原理的同学也不能松懈 毕竟 过一阵子不看又忘了-_-#

@zizizheng
Copy link

第七題
console.log(undefined === undefined); //false
結果應該為 true?

@adntin
Copy link

adntin commented Sep 26, 2017

@honwenle [] == ![] 解析器是这样解析的:左边 --> [].valueOf() 返回对象, 继续调用toString() --> "", 右边 --> false --> 0, 最终变成 "" == 0 , 所以返回true

@Joker0824
Copy link

console.log(undefined===undefined)//这里应该是true

@Tongym162
Copy link

第二条中 console,log(typeof a); //undefined,但是应该为number吧? 运行出来的结果也是这样
(function(){
var a = b = 3;
})();
console.log(typeof a)
console.log("a defined? " + (typeof a !== 'undefined')); // true
console.log("b defined? " + (typeof b !== 'undefined')); // true
VM232:4 number
VM232:5 a defined? true
VM232:6 b defined? true

@xiangwenhu
Copy link

var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log("outer func: this.foo = " + this.foo);
console.log("outer func: self.foo = " + self.foo);
(function() {
console.log("inner func: this.foo = " + this.foo);
console.log("inner func: self.foo = " + self.foo);
}());
}
};
myObject.func();

所以第三个输出会报错,因为 this 在可访问到的作用域内是 undefined
不是undefined吧,浏览器中是window, nodejs中等于global

@plh97
Copy link

plh97 commented Apr 2, 2018

绕晕了。。。我还是继续看js的调用栈补补基础

@rachelyao
Copy link

console.log(undefined === undefined); //true

@tianmingzuo
Copy link

问题14的关键点是push()方法, 与reverse()方法的关系不大:
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.push("Kiwi", "Lemon", "Pineapple"); //后插入单个元素:fruits.length=7, 即4+3
fruits.push(["Kiwi", "Lemon", "Pineapple"]); //后插入一个整体数组:fruits.length=5, 即 4+1

@tianmingzuo
Copy link

问题14:
var fruits = ["Banana", "Orange", "Apple", "Mango"];
若后插入一个整体数组:
fruits.push(["Kiwi", "Lemon", "Pineapple"]);
然后用slice(-1)方法, 则后插入的数组整体被弹出:
fruits.slice(-1);//Kiwi,Lemon,Pineapple

@tianmingzuo
Copy link

第19题: 在非if语句中, 0, '0', +0, -0, false, '', null, undefined, null, NaN都被视为false。然而, 在if分支语句判断时, '0', [0], []被视为true,因为他们并非空元素(not null)。
另外, 在if分支语句判断时,{}也被视为true。

@tianmingzuo
Copy link

在if分支语句判断时,只有0, false, '', null, undefined, NaN等6个值被视为false。

@juzhiyuan
Copy link

请问 @dwqs 第14条,为什么arr1arr2.push(arr3) 之前便输出下图结果呢?

image

@tianmingzuo
Copy link

@juzhiyuan: 你的问题是“JavaScript 是单线程, 为什么前面的语句会显示出程序完全执行后的结果?”。
要回答您的问题, 首先要明白:1. 数组是地址引用传递;2. console.log显示结果时程序已执行完毕,并非是程序正在执行时的结果。要想看到程序正在执行时每一步的结果,把其中的console.log换成alert语句,您就会看到所期待的结果。

@tianmingzuo
Copy link

@juzhiyuan:
var arr1 = "john".split(''); //j o h n
alert(arr1); //j o h n
var arr2 = arr1.reverse(); //reverse() 会改变数组本身,并返回原数组的引用。
alert(arr1); //n h o j
alert(arr2); //n h o j
alert(arr1===arr2); //true; arr2是对原数组arr1的引用,即arr2指向了arr1的地址。
var arr3 = "jones".split('');
alert(arr3); //j o n e s
var arr4=arr2.push(arr3);
alert(arr1); //n,h,o,j,j,o,n,e,s
alert(arr1.length); //5; arr3 as the last object(not separated) in arr1, so arr1.slice(-1) gives out the last
object(arr3). so length=4+1=5;
alert(arr2); //n,h,o,j,j,o,n,e,s
alert(arr1===arr2); //true, arr2指向了arr1的地址,改变arr2也就是改变了arr1。
alert(arr4); //5; The push() method returns the new array length; arr4=arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1)); //array 1: length=5 last=j,o,n,e,s
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1)); //array 2: length=5 last=j,o,n,e,s

@juzhiyuan
Copy link

@tianmingzuo 非常感谢!

@guonanci
Copy link

第3题语法错误看不下去:::

    (function() {
        console.log("inner func:  this.foo = " + this.foo);
        console.log("inner func:  self.foo = " + self.foo);
    }());

改成

    (function() {
        console.log("inner func:  this.foo = " + this.foo);
        console.log("inner func:  self.foo = " + self.foo);
    })();

@tianmingzuo
Copy link

tianmingzuo commented Oct 25, 2018 via email

@guonanci
Copy link

@tianmingzuo 嗯嗯,

@ThanksSirAlex
Copy link

reverse() 会改变数组本身,并返回原数组的引用。不是返回原数组吧,返回的是颠倒后的数组

确实是返回原数组,因为会改变数组本身,所以原数组已经已经颠倒了。

@jweboy
Copy link

jweboy commented Jan 29, 2019

求截图的 JS 语法规范的网站地址。

@mqliutie
Copy link

mqliutie commented Mar 7, 2019

@llxiaozz
Copy link

llxiaozz commented May 30, 2019

第3题语法错误看不下去:::

    (function() {
        console.log("inner func:  this.foo = " + this.foo);
        console.log("inner func:  self.foo = " + self.foo);
    }());

改成

    (function() {
        console.log("inner func:  this.foo = " + this.foo);
        console.log("inner func:  self.foo = " + self.foo);
    })();

都可以的喔,前面用+号都行。

@KamingInWork
Copy link

你的第十三题在我看来是错误的
输出console 都是5不是因为IIFE的问题,而是在构建JS执行上下文的函数环境的时候就给i声明了他作用域,这个时候
你将for (var i = 0; ...; i ++) 改成for (let i = 0; ...; i ++) , 直接输出正确的结果
另外,如果真的是IIFE的问题,请给个答案,我不太懂,求指教

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests