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

var list = readHugeList();

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

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

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

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

.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 =
innerVar =
globalVar =

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={},
={key:'b'},
={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 ((n > 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) {
.log(x);
})(2)
})(1);

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

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

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

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

var hero = {
: 'John Doe',
: 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() {
.log(this.length);
}

var obj = {
: 5,
: function(fn) {
fn();
[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;
.log(x);
}
.log(x);
.log(y);
})();

 
 
1
undefined
2

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

(function () {
var x, y; // outer and hoisted
try {
throw new Error();
} catch (x /* inner */) {
= 1; // inner x, not the outer one
= 2; // there is only one y, which is in the outer scope
.log(x /* inner */);
}
.log(x);
.log(y);
})();

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

var x = 21;
var girl = function () {
.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 = {
: 1,
: 2,
: {
: 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'];