【黑马程序员西安中心】33 个 JS 基本面试题及解答(二)

16、如果数组列表太大,以下递归代码将导致堆栈溢出。你如何解决这个问题,仍然保留递归模式?

 
  

var list = readHugeList();


var nextListItem = function() {

    var item = list.pop();


    if (item) {

        // process the list item...

        nextListItem();

    }

};

通过修改nextListItem函数可以避免潜在的堆栈溢出,如下所示:

扫描二维码关注公众号,回复: 1872560 查看本文章
 
  

var list = readHugeList();


var nextListItem = function() {

    var item = list.pop();


    if (item) {

        // process the list item...

        setTimeout( nextListItem, 0);

    }

};

堆栈溢出被消除,因为事件循环处理递归,而不是调用堆栈。当nextListItem运行时,如果item不为null,则将超时函数(nextListItem)推送到事件队列,并且函数退出,从而使调用堆栈清零。当事件队列运行超时事件时,将处理下一个项目,并设置一个计时器以再次调用nextListItem。因此,该方法从头到尾不经过直接递归调用即可处理,因此调用堆栈保持清晰,无论迭代次数如何。

17、什么是JavaScript中的“闭包”?举一个例子。

闭包是一个内部函数,它可以访问外部(封闭)函数的作用域链中的变量。闭包可以访问三个范围内的变量;具体来说: (1)变量在其自己的范围内, (2)封闭函数范围内的变量 (3)全局变量。

这里是一个例子:

 
  

var globalVar = "xyz";


(function outerFunc(outerArg) {

    var outerVar = 'a';


    (function innerFunc(innerArg) {

    var innerVar = 'b';


    console.log(

        "outerArg = " + outerArg + "\n" +

        "innerArg = " + innerArg + "\n" +

        "outerVar = " + outerVar + "\n" +

        "innerVar = " + innerVar + "\n" +

        "globalVar = " + globalVar);


    })(456);

})(123);

在上面的例子中,innerFunc,outerFunc和全局名称空间的变量都在innerFunc的范围内。上面的代码将产生以下输出:

 
  

outerArg = 123

innerArg = 456

outerVar = a

innerVar = b

globalVar = xyz

18、以下代码的输出是什么:

 
  

for (var i = 0; i < 5; i++) {

    setTimeout(function() { console.log(i); }, i * 1000 );

}

解释你的答案。如何在这里使用闭包?

显示的代码示例不会显示值0,1,2,3和4,这可能是预期的;而是显示5,5,5,5。

这是因为循环内执行的每个函数将在整个循环完成后执行,因此所有函数都会引用存储在i中的最后一个值,即5。

通过为每次迭代创建一个唯一的作用域,可以使用闭包来防止这个问题,并将该变量的每个唯一值存储在其作用域中,如下所示:

 
  

for (var i = 0; i < 5; i++) {

    (function(x) {

        setTimeout(function() { console.log(x); }, x * 1000 );

    })(i);

}

这会产生将0,1,2,3和4记录到控制台的可能结果。

在ES2015上下文中,您可以在原始代码中简单地使用let而不是var:

 
  

for (let i = 0; i < 5; i++) {

    setTimeout(function() { console.log(i); }, i * 1000 );

}

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));

解释你的答案。

该代码将输出以下四行:

 
  

0 || 1 = 1

1 || 2 = 1

0 && 1 = 0

1 && 2 = 2

在JavaScript中,都是||和&&是逻辑运算符,当从左向右计算时返回第一个完全确定的“逻辑值”。

或(||)运算符。在形式为X || Y的表达式中,首先计算X并将其解释为布尔值。如果此布尔值为真,则返回true(1),并且不计算Y,因为“或”条件已经满足。但是,如果此布尔值为“假”,我们仍然不知道X || Y是真还是假,直到我们评估Y,并将其解释为布尔值。

因此,0 || 1评估为真(1),正如1 || 2。

和(&&)运算符。在X && Y形式的表达式中,首先评估X并将其解释为布尔值。如果此布尔值为false,则返回false(0)并且不评估Y,因为“and”条件已失败。但是,如果这个布尔值为“真”,我们仍然不知道X && Y是真还是假,直到我们评估Y,并将其解释为布尔值。

然而,&&运算符的有趣之处在于,当表达式评估为“真”时,则返回表达式本身。这很好,因为它在逻辑表达式中被视为“真”,但也可以用于在您关心时返回该值。这解释了为什么,有点令人惊讶的是,1 && 2返回2(而你可能会期望它返回true或1)。

20 、下面的代码执行时输出是什么?说明。

 
  

console.log(false == '0')

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

该代码将输出:

 
  

true

false

在JavaScript中,有两套相等运算符。三重相等运算符===的行为与任何传统的相等运算符相同:如果两侧的两个表达式具有相同的类型和相同的值,则计算结果为true。然而,双等号运算符在比较它们之前试图强制这些值。因此,通常使用===而不是==。对于!== vs!=也是如此。

21、以下代码的输出是什么?解释你的答案。

 
  

var a={},

    b={key:'b'},

    c={key:'c'};


a[b]=123;

a[c]=456;


console.log(a[b]);

此代码的输出将是456(不是123)。

原因如下:设置对象属性时,JavaScript会隐式地将参数值串联起来。在这种情况下,由于b和c都是对象,它们都将被转换为“[object Object]”。因此,a [b]和a [c]都等价于[“[object Object]”],并且可以互换使用。因此,设置或引用[c]与设置或引用[b]完全相同。

22、以下代码将输出到控制台中.

 
  

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

该代码将输出10阶乘的值(即10!或3,628,800)。

原因如下:

命名函数f()以递归方式调用自身,直到它调用f(1),它简单地返回1.因此,这就是它的作用:

 
  

f(1): returns n, which is 1

f(2): returns 2 * f(1), which is 2

f(3): returns 3 * f(2), which is 6

f(4): returns 4 * f(3), which is 24

f(5): returns 5 * f(4), which is 120

f(6): returns 6 * f(5), which is 720

f(7): returns 7 * f(6), which is 5040

f(8): returns 8 * f(7), which is 40320

f(9): returns 9 * f(8), which is 362880

f(10): returns 10 * f(9), which is 3628800

23 、考虑下面的代码片段。控制台的输出是什么,为什么?

 
  

(function(x) {

    return (function(y) {

        console.log(x);

    })(2)

})(1);

输出将为1,即使x的值从未在内部函数中设置。原因如下:

正如我们的JavaScript招聘指南中所解释的,闭包是一个函数,以及创建闭包时在范围内的所有变量或函数。在JavaScript中,闭包被实现为“内部函数”;即在另一功能的主体内定义的功能。闭包的一个重要特征是内部函数仍然可以访问外部函数的变量。

因此,在这个例子中,因为x没有在内部函数中定义,所以在外部函数的作用域中搜索一个定义的变量x,该变量的值为1。

24、以下代码将输出到控制台以及为什么

 
  

var hero = {

    _name: 'John Doe',

    getSecretIdentity: function (){

        return this._name;

    }

};


var stoleSecretIdentity = hero.getSecretIdentity;


console.log(stoleSecretIdentity());

console.log(hero.getSecretIdentity());

这段代码有什么问题,以及如何解决这个问题。

该代码将输出:

 
  

undefined

John Doe

第一个console.log打印未定义,因为我们从hero对象中提取方法,所以stoleSecretIdentity()在_name属性不存在的全局上下文(即窗口对象)中被调用。

修复stoleSecretIdentity()函数的一种方法如下:

 
  

var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

25、创建一个函数,给定页面上的DOM元素,将访问元素本身及其所有后代(不仅仅是它的直接子元素)。对于每个访问的元素,函数应该将该元素传递给提供的回调函数。

该函数的参数应该是:

  • 一个 DOM 元素

  • 一个回调函数(以DOM元素作为参数)

访问树中的所有元素(DOM)是经典的深度优先搜索算法应用程序。以下是一个示例解决方案:

 
  

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

   }

}

27、在JavaScript中测试您的这些知识:以下代码的输出是什么?

 
  

var length = 10;

function fn() {

    console.log(this.length);

}


var obj = {

  length: 5,

  method: function(fn) {

    fn();

    arguments[0]();

  }

};


obj.method(fn, 1);

输出:

 
  

10

2

为什么不是10和5?

首先,由于fn作为函数方法的参数传递,函数fn的作用域(this)是窗口。 var length = 10;在窗口级别声明。它也可以作为window.length或length或this.length来访问(当这个===窗口时)。

方法绑定到Object obj,obj.method用参数fn和1调用。虽然方法只接受一个参数,但调用它时已经传递了两个参数;第一个是函数回调,其他只是一个数字。

当在内部方法中调用fn()时,该函数在全局级别作为参数传递,this.length将有权访问在Object obj中定义的var length = 10(全局声明)而不是length = 5。

现在,我们知道我们可以使用arguments []数组访问JavaScript函数中的任意数量的参数。

因此arguments0只不过是调用fn()。在fn里面,这个函数的作用域成为参数数组,并且记录参数[]的长度将返回2。

因此输出将如上所述。

28、考虑下面的代码。输出是什么,为什么?

 
  

(function () {

    try {

        throw new Error();

    } catch (x) {

        var x = 1, y = 2;

        console.log(x);

    }

    console.log(x);

    console.log(y);

})();

 
  

1

undefined

2

var语句被挂起(没有它们的值初始化)到它所属的全局或函数作用域的顶部,即使它位于with或catch块内。但是,错误的标识符只在catch块内部可见。它相当于:

 
  

(function () {

    var x, y; // outer and hoisted

    try {

        throw new Error();

    } catch (x /* inner */) {

        x = 1; // inner x, not the outer one

        y = 2; // there is only one y, which is in the outer scope

        console.log(/* inner */);

    }

    console.log(x);

    console.log(y);

})();

29、这段代码的输出是什么?

 
  

var x = 21;

var girl = function () {

    console.log(x);

    var x = 20;

};

girl ();

21,也不是20,结果是‘undefined’的

这是因为JavaScript初始化没有被挂起。

(为什么它不显示21的全局值?原因是当函数执行时,它检查是否存在本地x变量但尚未声明它,因此它不会查找全局变量。)

30、你如何克隆一个对象?

 
  

var obj = {a: 1 ,b: 2}

var objclone = Object.assign({},obj);

现在objclone的值是{a:1,b:2},但指向与obj不同的对象。

但请注意潜在的缺陷:Object.clone()只会执行浅拷贝,而不是深拷贝。这意味着嵌套的对象不会被复制。他们仍然引用与原始相同的嵌套对象:

 
  

let obj = {

    a: 1,

    b: 2,

    c: {

        age: 30

    }

};


var objclone = Object.assign({},obj);

console.log('objclone: ', objclone);


obj.c.age = 45;

console.log('After Change - obj: ', obj); // 45 - This also changes

console.log('After Change - objclone: ', objclone); // 45

31、此代码将打印什么?

 
  

for (let i = 0; i < 5; i++) {

    setTimeout(function() { console.log(i); }, i * 1000 );

}

它会打印0 1 2 3 4,因为我们在这里使用let而不是var。变量i只能在for循环的块范围中看到。

32、以下几行输出什么,为什么?

 
  

console.log(1 < 2 < 3);

console.log(3 > 2 > 1);

第一条语句返回true,如预期的那样。

第二个返回false是因为引擎如何针对<和>的操作符关联性工作。它比较从左到右,所以3> 2> 1 JavaScript翻译为true> 1. true具有值1,因此它比较1> 1,这是错误的。

33、如何在数组的开头添加元素?最后如何添加一个?

 
  

var myArray = ['a', 'b', 'c', 'd'];

myArray.push('end');

myArray.unshift('start');

console.log(myArray); // ["start", "a", "b", "c", "d", "end"]

使用ES6,可以使用扩展运算符:

 
  

myArray = ['start', ...myArray];

myArray = [...myArray, 'end'];

或者,简而言之:

 
  

myArray = ['start', ...myArray, 'end'];

猜你喜欢

转载自blog.csdn.net/qq_39581763/article/details/80763920