经典前端题1

经典前端题(1)

经典前端题(1)

用typeof来判断对象的潜在陷阱

问:使用 typeof bar === “object” 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个陷阱?

尽管 typeof bar === “object” 是检查 bar 是否对象的可靠方法,令人惊讶的是在JavaScript中 null 也被认为是对象!

因此,令大多数开发人员惊讶的是,下面的代码将输出 true 控制台:

1
2
3
var bar = null;
console.log(typeof bar === "object");
// true!

只要清楚这一点,同时检查 bar 是否为 null,就可以很容易地避免问题:

1
2
3
4
5
console.log(
(bar !== null)
&&
(typeof bar === "object")
);

下面的代码将输出什么到控制台(是否加var)

1
2
3
4
5
6
(function(){  
var a = b = 3;
})();

console.log(typeof a == 'undefined');
console.log(typeof b == 'undefined');

部分开发者就会认为,变量a和b都是声明定义在匿名函数内部的,属于函数内的局部变量,如果要在函数外部进行打印调用,a和b则为undefined。所以最后两行代码打印的结果都是true。

然而结果并非如此,之所以这么理解的原因是,他们把

1
var a = b = 3;

这句代码错误地理解为等同于:

1
2
var b = 3;
var a = b;

而事实上,它等同于:

1
2
b = 3;
var a = b;

这个时候的变量b声明是不加关键字var的,也就是说变量b是全局变量(隐式全局变量)。

所以最后我们得到的结果分别是:true和false。

在严格模式下(使用 use strict),又会是什么结果?

这里就要注意一点了,在严格模式下我们运行后得到的结果是:

1
报错:b is not undefined

这也正是因为严格模式的这个特点,可以避免很多不必要的bug(全局变量污染)。

关于this指向的常见面试题

看下面的代码,你觉得输出的结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function() {
console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();

在外部函数中, this 和 self 两者都指向了 myObject ,因此两者都可以正确地引用和访问 foo 。

在内部函数中, this不再指向myObject.其结果可以console.log(this)打印出来,发现是:Windows对象。(在ECMA 5之前,在内部函数中的this 将指向全局的window对象;反之,因为作为ECMA 5,内部函数中的功能this 是未定义的。)

上面的代码将输出以下内容到控制台:

1
2
3
4
bar
bar
undefined
bar

use strict 有什么意义和好处?

use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。
严格模式是ES5引入的,更好的将错误检测引入代码的方法.包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。

设立”严格模式”的目的,主要有以下几个:

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
变量必须先声明,再使用
function test(){
"use strict";
foo = 'bar'; // Error
}

不能对变量执行delete操作
var foo = "test";
function test(){}

delete foo; // Error
delete test; // Error

function test2(arg) {
delete arg; // Error
}
对象的属性名不能重复
{ foo: true, foo: false } // Error

禁用eval()

函数的arguments参数
setTimeout(function later(){
// do stuff...
setTimeout( later, 1000 );
}, 1000 );

禁用with(){}

不能修改arguments
不能在函数内定义arguments变量
不能使用arugment.caller和argument.callee。因此如果你要引用匿名函数,需要对匿名函数命名。

进入标志

1
"use strict";

老版本的浏览器会把它当作一行普通字符串,加以忽略。

如何调用

针对整个脚本文件

将”use strict”放在脚本文件的第一行,则整个脚本都将以”严格模式”运行。如果这行语句不在第一行,则无效,整个脚本以”正常模式”运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

(严格地说,只要前面不是产生实际运行结果的语句,”use strict”可以不在第一行,比如直接跟在一个空的分号后面。)

1
2
3
4
5
6
7
<script>
  "use strict";
  console.log("这是严格模式。");
</script>
<script>
  console.log("这是正常模式。");kly, it's almost 2 years ago now. I can admit it now - I run it on my school's network that has about 50 computers.
</script>

上面的代码表示,一个网页中依次有两段Javascript代码。前一个script标签是严格模式,后一个不是。

针对单个函数

将”use strict”放在函数体的第一行,则整个函数以”严格模式”运行。

1
2
3
4
5
6
7
function strict(){
  "use strict";
  return "这是严格模式。";
}
function notStrict() {
  return "这是正常模式。";
}

脚本文件的变通写法

因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

1
2
3
4
5
6
(function (){

  "use strict";
  // some code here

})();

小心javascript自动插入分号机制

在《JavaScript语言精粹》这本书里,这个“自动插入分号”机制被划入到了JavaScript的毒瘤里面,与之并列的前面的全局变量。

有些时候,不合时宜地插入分号,会导致严重的后果。 比如一个return语句要正确返回一个值,这个值的表达式的开始部分必须和return位于同一行

今天,我们就来聊聊这个知识点,看下面的代码函数,它们会返回什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo1(){  
return {
bar: "hello"
};
}

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

console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

将产生:

1
2
3
4
foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined

这不仅是令人惊讶,而且特别让人困惑的是, foo2()返回的是undefined,也没有任何错误抛出。
原因是这样的,当碰到 foo2()中包含return语句的代码行(代码行上没有其他任何代码),分号会立即自动插入到返回语句之后。请仔细留意上面两个函数中return的不同之处,foo2函数的return是单独一行的。

也不会抛出错误,因为代码的其余部分是完全有效的,即使它没有得到调用或做任何事情(相当于它就是是一个未使用的代码块,定义了等同于字符串 “hello”的属性 bar)。

所以,在使用return语句的时候,要留意javascript的这个特点,尽可能不要将return关键字写在独立的一行,避免不必造成不必要的错误。

NaN 是什么?如何测试一个值是否等于 NaN ?

NaN 属性代表一个“不是数字”的值。这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字。例如: “abc” / 4。
首先,虽然 NaN 意味着“不是数字”,但是它的类型,不管你信不信,是 Number:

1
2
console.log(typeof NaN === "number");  
// logs "true"

此外, NaN 和任何东西比较,甚至是它自己本身,结果是false:

1
2
console.log(NaN === NaN);  
// logs "false"

一种半可靠的方法来测试一个数字是否等于 NaN,是使用内置函数 isNaN(),但即使使用 isNaN() 依然并非是一个完美的解决方案。

一个更好的解决办法是使用 value !== value,如果值等于NaN,只会产生true。因为只有NaN 这货,才会自己不等于自己。

另外,ES6提供了一个新的Number.isNaN() 函数,这是一个不同的函数,并且比老的全局 isNaN() 函数更可靠。

Number.isNaN()和全局函数isNaN() 相比,该方法不会强制将参数转换成数字,只有在参数是真正的数字类型,且值为 NaN 的时候才会返回 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Number.isNaN(NaN);        // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0) // true

// 下面这几个如果使用全局的 isNaN() 时,会返回 true。
Number.isNaN("NaN"); // false,字符串 "NaN" 不会被隐式转换成数字 NaN。
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN("blabla"); // false

// 下面的都返回 false
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN(37);
Number.isNaN("37");
Number.isNaN("37.37");
Number.isNaN("");
Number.isNaN(" ");

下面的代码运行结果是什么?(自动类型转换)

JavaScript(ECMAScript)是一种弱类型语言,它可对值进行自动类型转换,以适应正在执行的操作。

让我们通过下面的6个例子来说明一下。
例子1:

1
console.log(1 +  "2" + "2");

得到的结果是:
"122"
解析: 1 + “2” + “2” 输出: “122” 。 1 + “2” 是执行的第一个操作。由于其中一个运算对象 “2” 是字符串,JavaScript会假设它需要执行字符串连接,因此,会将 1 的类转换为 “1” , 1+”2” 结果就是 “12” 。然后, “12”+”2” 就是 “122” 。

例子2:

1
console.log(1 +  +"2" + "2");

得到的结果是:
"32"
解析: 1 + +”2” + “2” 输出: “32” 说明:根据运算的顺序,要执行的第一个运算是 +”2” (第一个 “2” 前面的额外 + 被视为一元运算符)。

因此,JavaScript将 “2” 的类型转换为数字,然后应用一元 + 号(即将其视为一个正数)。其结果就是得到一个数字 2 ,接下来的运算就是 1 + 2 ,这当然是 3 。

然后我们需要在一个数字和一个字符串之间进行运算(即, 3 和 “2”),同样的,JavaScript会将数值类型转换为字符串,并执行字符串的连接,产生 “32” 。

例子3:

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

得到的结果是:
"02"
解析: 1 + -“1” + “2” 输出: “02” 。这里的解释和前一个例子相同,不同的地方是此处的一元运算符是 - 而不是 + 。

先是 “1” 变为 1 ,然后当应用 - 时又变为了 -1 ,然后将其与 1 相加,结果为 0 ,再将其转换为字符串,连接最后的 “2” 运算对象,得到 “02” 。

例子4:

1
console.log(+"1" +  "1" + "2");

得到的结果是:
"112"
解析: +”1” + “1” + “2” 输出: “112” 。虽然第一个运算对象 “1” 因为前缀的一元 + 运算符类型转换为数值,但当连接到第二个运算对象 “1” 的时候,又立即转换回字符串,然后又和最后的运算对象 “2” 连接,产生了字符串 “112” 。

例子5:

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

得到的结果是:
"NaN2"
解析: “A” - “B” + “2” 输出: “NaN2” 。由于运算符 - 不能被应用于字符串,并且 “A” 和 “B” 都不能转换成数值,因此, “A” - “B” 的结果是 NaN ,然后再和字符串 “2” 连接,得到 “NaN2” 。

例子6:

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

得到的结果是:
NaN
解析: “A” - “B” + 2 输出: NaN 。参见前一个例子, “A” - “B” 结果为 NaN 。但是,应用任何运算符到 NaN 与其他任何的数字运算对象,结果仍然是 NaN 。

关于逻辑运算符,下面代码运行的结果是什么?

逻辑运算符用于测定变量或值之间的逻辑。在我们实际开发过程中十分常用,所以面试官会把逻辑运算符的知识点当作考察应聘者的内容之一。

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

或 || 运算符。在 X||Y 的表达式中,首先计算 X 并将其解释执行为一个布尔值。

如果这个布尔值true,那么返回true(1),不再计算 Y ,因为“或”的条件已经满足。

如果这个布尔值为false,那么我们仍然不能知道 X||Y 是真是假,直到我们计算 Y ,并且也把它解释执行为一个布尔值。

因此, 0||1 的计算结果为true(1),同理计算 1||2 。

与 && 运算符。在 X&&Y 的表达式中,首先计算 X 并将其解释执行为一个布尔值。

如果这个布尔值为 false,那么返回 false(0),不再计算 Y ,因为“与”的条件已经失败。

如果这个布尔值为true,但是,我们仍然不知道 X&&Y 是真是假,直到我们去计算 Y ,并且也把它解释执行为一个布尔值。 .

不过,关于 && 运算符有趣的地方在于,当一个表达式计算为“true”的时候,那么就返回表达式本身。

这很好,虽然它在逻辑表达式方面计算为“真”,但如果你希望的话也可用于返回该值。这就解释了为什么,有些令人奇怪的是, 1 && 2返回 2,而不是你以为的可能返回 true 或 1。

1
2
3
4
console.log( 0 || 1 );
console.log( 1 || 2 );
console.log( 0 && 1 );
console.log( 1 && 2 );

答案:
···
1
1
0
2
···

以下代码将输出什么?(设置对象的属性)

看下面的代码,输出的结果是什么?并解释你的答案。

1
2
3
4
5
6
7
8
var a={},
b={key:'b'},
c={key:'c'};

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

console.log(a[b]);

不少人可能会以为答案是123 ,但结果是 456 ,为什么会是这样子的呢。

原因是我们忽略了一个知识点:JavaScript在设置对象的属性的时候,会暗中字符串化参数值

在这里例子中,由于 b 和 c都是对象,把它们设置为对象a的参数,它们都将被转换为"[object Object]"

结果就是, a[b] 和 a[c] 都相当 “[object Object]” ,而后者会将前者的值覆盖。

因此,设置或引用 a[c] 和设置或引用 a[b] 完全相同。所以得到的答案是 456 。
把对象b作为a的属性的时候,会先调用对象b的toString()方法。

1
2
3
var b={key:'b'};
console.log(b.toString());
//[object Object]

c也类似,因为转换后得自符串是一样的,所以后面的会覆盖前面的

关于this指向,以下代码将输出什么

看下面的代码,输出的结果是什么?并解释你的答案。

1
2
3
4
5
6
7
8
9
10
11
var person = {
_name: 'I am John',
sayHello: function (){
return this._name;
}
};

var sayHello = person.sayHello;

console.log(sayHello());
console.log(person.sayHello());

代码运行的结果是:

1
2
undefined
I am John

第二个打印 person.sayHello()的结果是: I am John这个好理解。

为什么第一次打印出来的结果是 undefined 呢?明明是直接赋值过去的呀。这里涉及到的知识点就是:this的指向。

在执行sayHello()的时候,当访问到 this._name 时,此时的this已经不再是 person 对象,而是全局窗口对象,也就是 widnow 对象。

参考文章:


本作品采用 知识共享署名 2.5 中国大陆许可协议 进行许可,欢迎转载,但转载请注明来自JayMo,并保持转载后文章内容的完整。本人保留所有版权相关权利。
本文永久链接:http://jaymo666.github.io/2018/04/16/经典前端题1/