经典前端题(1)
用typeof来判断对象的潜在陷阱
问:使用 typeof bar === “object” 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个陷阱?
尽管 typeof bar === “object” 是检查 bar 是否对象的可靠方法,令人惊讶的是在JavaScript中 null 也被认为是对象!
因此,令大多数开发人员惊讶的是,下面的代码将输出 true 控制台:1
2
3var bar = null;
console.log(typeof bar === "object");
// true!
只要清楚这一点,同时检查 bar 是否为 null,就可以很容易地避免问题:1
2
3
4
5console.log(
(bar !== null)
&&
(typeof bar === "object")
);
下面的代码将输出什么到控制台(是否加var)
1 | (function(){ |
部分开发者就会认为,变量a和b都是声明定义在匿名函数内部的,属于函数内的局部变量,如果要在函数外部进行打印调用,a和b则为undefined。所以最后两行代码打印的结果都是true。
然而结果并非如此,之所以这么理解的原因是,他们把1
var a = b = 3;
这句代码错误地理解为等同于:1
2var b = 3;
var a = b;
而事实上,它等同于:1
2b = 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
13var 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 | bar |
use strict 有什么意义和好处?
use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。
严格模式是ES5引入的,更好的将错误检测引入代码的方法.包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。
设立”严格模式”的目的,主要有以下几个:
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫。
1 | 变量必须先声明,再使用 |
进入标志
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
7function 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
17function 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
4foo1 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
2console.log(typeof NaN === "number");
// logs "true"
此外, NaN 和任何东西比较,甚至是它自己本身,结果是false:1
2console.log(NaN === NaN);
// logs "false"
一种半可靠的方法来测试一个数字是否等于 NaN,是使用内置函数 isNaN(),但即使使用 isNaN() 依然并非是一个完美的解决方案。
一个更好的解决办法是使用 value !== value,如果值等于NaN,只会产生true。因为只有NaN 这货,才会自己不等于自己。
另外,ES6提供了一个新的Number.isNaN()
函数,这是一个不同的函数,并且比老的全局 isNaN() 函数更可靠。
Number.isNaN()
和全局函数isNaN()
相比,该方法不会强制将参数转换成数字,只有在参数是真正的数字类型,且值为 NaN 的时候才会返回 true。
1 | Number.isNaN(NaN); // true |
下面的代码运行结果是什么?(自动类型转换)
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 | console.log( 0 || 1 ); |
答案:
···
1
1
0
2
···
以下代码将输出什么?(设置对象的属性)
看下面的代码,输出的结果是什么?并解释你的答案。1
2
3
4
5
6
7
8var 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
3var b={key:'b'};
console.log(b.toString());
//[object Object]
c也类似,因为转换后得自符串是一样的,所以后面的会覆盖前面的
关于this指向,以下代码将输出什么
看下面的代码,输出的结果是什么?并解释你的答案。
1 | var person = { |
代码运行的结果是:1
2undefined
I am John
第二个打印 person.sayHello()
的结果是: I am John
这个好理解。
为什么第一次打印出来的结果是 undefined 呢?明明是直接赋值过去的呀。这里涉及到的知识点就是:this的指向。
在执行sayHello()
的时候,当访问到 this._name 时,此时的this已经不再是 person 对象,而是全局窗口对象,也就是 widnow 对象。
参考文章:
- Javascript 严格模式use strict详解
- 微信公众号:web前端教程
- MDN web docs
本作品采用 知识共享署名 2.5 中国大陆许可协议 进行许可,欢迎转载,但转载请注明来自JayMo,并保持转载后文章内容的完整。本人保留所有版权相关权利。
本文永久链接:http://jaymo666.github.io/2018/04/16/经典前端题1/