根据StackOverflow调查, 自2014年一来,JavaScript是最流行的编程语言。当然,这也在情理之中,毕竟1/3的开发工作都需要一些JavaScript知识。因此,如果你希望在成为一个开发者,你应该学会这门语言。
这篇博客的主要目的是将所有面试中常见的概念总结,方便你快速去了解。
(译者按:鉴于本文内容过长,方便阅读,将分为三篇博客来翻译。)
类型和类型转换
在JavaScript中有7个内置类型:null
,undefined
,boolean
,number
,string
,object
,和symbol
(ES6)。
除了object
以外,其它都叫做基本类型。
typeof 0 // number typeof true // boolean typeof 'Hello' // string typeof Math // object typeof null // object !! typeof Symbol('Hi') // symbol (New ES6) |
- Null vs. Undefined
Undefined表示未定义。对于没有初始化的变量、函数调用时候未提供的函数参数、缺失的对象属性,它们的默认值就是undefined
。如果一个函数没有返回语句,那么默认的返回值也是undefined
。
NUll表示值为空。一个变量我们可以将其赋值为null
,表示当前的没有值。
- 隐式转换
请看下面的例子:
var name = 'Joey'; if (name) { console.log(name + " doesn't share food!") // Joey doesn’t share food! } |
在if
语句的条件判断中,name
从字符串转换为布尔型。在if
的代码块中,在控制台将name
原原本本打印出来。你知道在什么情况下字符串会转换为真,什么时候为假么?
""
,0
, null
,undefined
, NaN
, false
会自动转换为false
。其它的都会转换为真:
Boolean(null) // false Boolean('hello') // true Boolean('0') // true Boolean(' ') // true Boolean([]) // true Boolean(function(){}) // true |
空数组、对象、函数定义都会自动转换为真。
- String & Number之间的转换
第一个你要非常小心的是+
操作符。因为它同时用于数字相加和字符串拼接。
*
,/
,-
只用于数字运算,当这些操作符和字符串一起使用,那么字符串会被强制转换为数字。
1 + "2" = "12" "" + 1 + 0 = "10" "" - 1 + 0 = -1 "-9\n" + 5 = "-9\n5" "-9\n" - 5 = -14 "2" * "3" = 6 4 + 5 + "px" = "9px" "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN null + 1 = 1 |
- == vs. ===
一个广泛被接受的认知就是:==
判断值是否相等,===
同时判断值是否相等和类型是否相同。但是,这里有些误解。
实际上,==
在验证相等性的时候,会对类型不同的值做一个类型转换。===
对要判断的值不做类型转换。
2 == '2' // True 2 === '2' // False undefined == null // True undefined === null // False |
类型转换有很多取巧的地方,要注意:
let a = '0'; console.log(Boolean(a)); // True let b = false; console.log(Boolean(b)); // False ``` 你认为下面的相等判断会输出什么值呢? ```js console.log(a == b); |
实际上会返回true。知道为什么吗?
如果你将一个布尔类型的和非布尔类型的判断,JavaScript会将布尔类型的转换为数字然后再比对。
执行过程如下:
'0' == false (1) '0' == 0 (2) 0 == 0 (3) |
所以,最终变成了0==0
,当然返回true啦。
如果你想看完整的资料,请查看ES5的官方文档。
如果想看cheat sheet, 点击这里。
一些比较容易掉坑的比较,我在这里列出来:
false == "" // true false == [] // true false == {} // false "" == 0 // true "" == [] // true "" == {} // false 0 == [] // true 0 == {} // false 0 == null // false |
值 vs. 引用
对于基本类型的值,赋值是通过值拷贝的形式;比如:null
,undefined
,boolean
,number
,string
和ES6的symbol
。对于复杂类型的值,通过引用拷贝的形式赋值。比如:对象、对象包括数组和函数。
var a = 2; // 'a' hold a copy of the value 2. var b = a; // 'b' is always a copy of the value in 'a' b++; console.log(a); // 2 console.log(b); // 3 var c = [1,2,3]; var d = c; // 'd' is a reference to the shared value d.push( 4 ); // Mutates the referenced value (object) console.log(c); // [1,2,3,4] console.log(d); // [1,2,3,4] /* Compound values are equal by reference */ var e = [1,2,3,4]; console.log(c === d); // true console.log(c === e); // false |
如果想对复杂类型的值进行值拷贝,你需要自己去对所有子元素进行拷贝。
const copy = c.slice() // 'copy' 即使copy和c相同,但是copy指向新的值 console.log(c); // [1,2,3,4] console.log(copy); // [1,2,3,4] console.log(c === copy); // false |
Fundebug提供实时、专业的错误监控服务,为您的线上代码保驾护航,欢迎大家免费使用!
作用域(Scope)
作用域值程序的执行环境,它包含了在当前位置可访问的变量和函数。
全局作用域是最外层的作用域,在函数外面定义的变量属于全局作用域,可以被任何其他子作用域访问。在浏览器中,window对象就是全局作用域。
局部作用域是在函数内部的作用域。在局部作用域定义的变量只能在该作用域以及其子作用域被访问。
function outer() { let a = 1; function inner() { let b = 2; function innermost() { let c = 3; console.log(a, b, c); // 1 2 3 } innermost(); console.log(a, b); // 1 2 — 'c' is not defined } inner(); console.log(a); // 1 — 'b' and 'c' are not defined } outer(); |
你可以将作用域想象成一系列不断变小的门。如果一个个子不高的人可以穿过最小的门(局部最小作用域),那么必然可以穿过任何比它大的门(外部作用域)。
提升(Hoisting)
在编译过程中,将var
和function
的定义移动到他们作用域最前面的行为叫做提升。
整个函数定义会被提升。所以,你可以在函数还未定义之前调用它,而不用担心找不到该函数。
console.log(toSquare(3)); // 9 function toSquare(n){ return n*n; } |
变量只会被部分提升。而且只有变量的声明会被提升,赋值不会动。
let
和const
不会被提升。
{ /* Original code */ console.log(i); // undefined var i = 10 console.log(i); // 10 } { /* Compilation phase */ var i; console.log(i); // undefined i = 10 console.log(i); // 10 } // ES6 let & const { console.log(i); // ReferenceError: i is not defined const i = 10 console.log(i); // 10 } { console.log(i); // ReferenceError: i is not defined let i = 10 console.log(i); // 10 } |
函数表达式和函数声明
- 函数表达式
一个函数表达式是在函数执行到函数表达式定义的位置才开始创建,并被使用。它不会被提升。
var sum = function(a, b) { return a + b; } |
- 函数声明
函数声明的函数可以在文件中任意位置调用,因为它会被提升。
function sum(a, b) { return a + b; } |
变量:var,let和const
在ES6之前,只能使用var
来声明变量。在一个函数体中声明的变量和函数,周围的作用域内无法访问。在块作用域if
和for
中声明的变量,可以在if
和for
的外部被访问。
注意:如果没有使用var
,let
或则const
关键字声明的变量将会绑定到全局作用域上。
function greeting() { console.log(s) // undefined if(true) { var s = 'Hi'; undeclaredVar = 'I am automatically created in global scope'; } console.log(s) // 'Hi' } console.log(s); // Error — ReferenceError: s is not defined greeting(); console.log(undeclaredVar) // 'I am automatically created in global scope' |
ES6的let
和const
都是新引入的关键字。它们不会被提升,而且是块作用域。也就是说被大括号包围起来的区域声明的变量外部将不可访问。
let g1 = 'global 1' let g2 = 'global 2' { /* Creating a new block scope */ g1 = 'new global 1' let g2 = 'local global 2' console.log(g1) // 'new global 1' console.log(g2) // 'local global 2' console.log(g3) // ReferenceError: g3 is not defined let g3 = 'I am not hoisted'; } console.log(g1) // 'new global 1' console.log(g2) // 'global 2' |
一个常见的误解是:使用const
声明的变量,其值不可更改。准确地说它不可以被重新赋值,但是可以更改。
const tryMe = 'initial assignment'; tryMe = 'this has been reassigned'; // TypeError: Assignment to constant variable. // You cannot reassign but you can change it… const array = ['Ted', 'is', 'awesome!']; array[0] = 'Barney'; array[3] = 'Suit up!'; console.log(array); // [“Barney”, “is”, “awesome!”, “Suit up!”] const airplane = {}; airplane.wings = 2; airplane.passengers = 200; console.log(airplane); // {passengers: 200, wings: 2}
请理解 闭包的一个优势在于数据隔离。我们同样用一个例子来说明:
在 我们来看一个面试题:下面的代码有什么问题,如何修复?
上面的代码输出的结果全部都一样:”The value undefined is at index: 4”。因为所有在 这个问题可以改用
当然,还有一个方法,使用
立即调用的函数表达式(Immediate Invoked Function Expression)(IIFE)一个IIFE是一个函数表达式在定义之后立即被调用。常用在你想对一个新声明的变量创建一个隔离的作用域。
使用IIFE可以:
环境(Context)我们往往容易将环境(Context)和作用域(Scope)搞混,我来简单解释一下:
函数调用:call, apply, bind这三个方法都是为了将this绑定到函数,区别在于调用的方式。
注意:如果你将数组传入 ES6允许使用新的操作符将数组变换为一个序列。
this关键字要理解JavaScript中 下面有序地列出了判断
注意:下面的情况中,
严格(Strict)模式如果你使用了
对于在严格(strict)模式和测试阶段都没有发现的bug,不妨接入线上实时监控插件Fundebug。 作者Fundebug以及本文地址: https://blog.fundebug.com/2018/01/15/the-definitive-javascript-handbook-for-a-developer-interview/ |