一 目录
不折腾的前端,和咸鱼有什么区别
二 var/let/const
返回目录
var
:
var
可以重复声明- 作用域:全局作用域 和 函数作用域
- 会进行预解析
let
:
- 统一作用域下不能重复声明
- 作用域:全局作用域 和 块级作用域
{}
- 不进行预解析
let
和var
比较:
var
声明的变量只能是全局或者整个函数块的let
允许声明一个在作用域限制在块级的变量、语句或者表达式(块级作用域)let
不能重复声明let
存在临时死区(temporal dead zone)- let` 不会被预解析(hoisting)
const
:
let
有的它也有- 初始化必须赋值
- 赋值后不能改动类型
三 暂时性死区
返回目录
只要块级作用域内存在 let
/const
命令,它所声明的变量就绑定这个区域,不再受外部的影响。
在代码块内,使用 let
/const
声明变量之前,该变量都是不可用的,所以叫 “暂时性死区”。
看一道题目:
下面代码输出什么?
let a = 1;
let test = function() {
console.log(a);
a++;
}
test();
下面代码输出什么?
let a = 1;
let test = function() {
console.log(a);
let a = 2;
a++;
}
test();
第一道题输出:1
第二道题输出:Uncaught ReferenceError: Cannot access 'a' before initialization
其原因是在同一个 block
中,let
在后面重新定义的,那么就不能在之前引用该变量。同时,也不能取嵌套外层的值。
四 函数作用域和块级作用域
返回目录
- 函数作用域:在 JavaScript 中定义一个函数,函数内部的变量只能通过函数内部访问,同时它可以修改和影响外部变量。
- 块级作用域:变量在离开定义的块级代码后立即被回收,存在暂时性死区的特性。
在 ES5 中只有全局作用域和函数作用域,没有块级作用域,这会带来些许麻烦:
- 内层变量覆盖外层变量
- 循环的变量泄露为全局变量
内层变量覆盖外层变量
var tmp = new Date();
function f(){
console.log(tmp);
if(false){
var tmp = "hello";
}
}
f(); // undefined
循环的变量泄露为全局变量
for (var i = 0; i < 3; i++) {
}
console.log(i); // 3
通过 var
声明的变量或者非严格模式下(non-strict mode)创建的函数声明没有块级作用域。
var x = 1;
{
var x = 2;
}
console.log(x); // 2
所以,为了解决这个问题,ES6 新增了块级作用域:
- 块语句用于组合零个或者多个语句,这个块由一对大括号
{}
界定。
ES5 形成块级作用域的方法
(function() {
})()
ES6 通过 {} 界定
{
var x = 1; // 或者 const x = 1;
}
五 变量提升和函数提升
返回目录
关于为什么进行变量提升和函数提升,这个问题一直没有明确的答案。
函数提升就是为了解决相互递归的问题,大体上可以解决自下而上的顺序问题。
// 验证偶数
function isEven(n) {
if (n === 0) {
return true;
}
return isOdd(n - 1);
}
console.log(isEven(2)); // true
// 验证奇数
function isOdd(n) {
if (n === 0) {
return false;
}
return isEven(n - 1);
}
如果没有函数提升,而是按照自下而上的顺序,当 isEven
函数被调用时,isOdd
函数还未声明,所以在 isEven
内部无法调用 isOdd
函数。
而变量提升的缘故,这里就不太清楚了,回答下来就是【设计如此】。
5.1 详细讲解:变量提升、函数提升和暂时性死区
返回目录
那么,变量提升是怎样的呢?
console.log(a);
var a = 10;
在这段脚本中,会输出 undefined
,为什么?
var a;
console.log(10);
a = 10;
看上面代码,在 JavaScript 解析的过程中,会将 a
提取到上面进行声明。
而 console.log(a)
打印的时候,因为 a
声明了但是没有填写值,所以是 undefined
。
而这段代码换成 let
或者 const
语句呢?就会出现 暂时性死区。
console.log(a);
let a = 10;
输出:
VM196:1 Uncaught ReferenceError: Cannot access 'a' before initialization
OK,下面进行 变量提升和暂时性死区 小总结:
- 在
var
定义的变量前面进行打印,因为变量提升缘故,所以可以打印undefined
- 在
let
、const
定义的变量前面进行打印,因为暂时性死区问题,所以会报错。
再看看函数提升:
var foo = 3;
function getFoo() {
var foo = foo || 5;
console.log(foo); // 输出 5
}
getFoo();
记住一句话:
- 函数是一等公民
所以,上面的代码,JavaScript 解析会变成:
function getFoo() {
var foo;
foo = foo || 5;
console.log(foo);
}
var foo;
foo = 3;
getFoo();
看,这就是 函数提升。
5.2 题目 1
返回目录
function getFoo() {
foo();
var foo = function() {
console.log(1);
}
foo();
function foo() {
console.log(2);
}
foo();
}
getFoo();
请问上面代码输出什么?
2
1
1
我们将其换成变量和函数提升后的效果便可得知:
function getFoo() {
var foo;
function foo() {
console.log(2);
}
foo(); // 2
foo = function() {
console.log(1);
}
foo(); // 1
foo(); // 1
}
getFoo();
5.3 题目 2
返回目录
console.log(a);
var a = 10;
var a = 100;
console.log(a);
function a() {
console.log('a');
}
function a() {
console.log('aa');
}
a();
请问上面代码输出什么?
答案:
[Function: a]
100
100
5.4 题目 3
返回目录
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a);
请问上面代码输出什么?
答案:1
5.5 题目 4
返回目录
function foo() {
function bar() {
return 3;
}
return bar();
function bar() {
return 8;
}
}
console.log(foo());
请问上面代码输出什么?
答案:8
5.6 题目 5
返回目录
var foo = { n: 1 };
(function(foo) {
console.log(foo.n);
foo.n = 3;
foo = { n: 2 };
console.log(foo.n);
})(foo)
console.log(foo.n);
请问上面代码输出什么?
- A:1 2 3
- B:undefined 2 1
- C:报错
- D:1 2 1
答案:
var foo = { n: 1 };
(function(foo){ // 形参 foo 同实参 foo 一样指向同一片内存空间,这个空间里的 n 的值为 1
var foo; // 优先级低于形参,无效。
console.log(foo.n); // 输出 1
foo.n = 3; // 形参与实参 foo 指向的内存空间里的 n 的值被改为 3
foo = { n: 2 }; // 形参 foo 指向了新的内存空间,里面 n 的值为 2.
console.log(foo.n); // 输出新的内存空间的 n 的值
})(foo);
console.log(foo.n); // 实参 foo 的指向还是原来的内存空间,里面的 n 的值为 3.
六 判断变量
返回目录
JavaScript 判断变量的方式有:
typeof(variable)
variable instanceof Array
variable.constructor = Array
Object.prototype.toString.call(variable)
它们都有哪些区别呢?
6.1 typeof
返回目录
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = { name: 'jsliang', age: 25 };
var func = function () { console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error = new Error();
console.log(
typeof num, // number
typeof str, // string
typeof bool, // boolean
typeof arr, // object
typeof json, // object
typeof func, // function
typeof und, // undefined
typeof nul, // object
typeof date, // object
typeof reg, // object
typeof error, // object
);
typeof
能区分的有:
number
string
boolean
undefined
function
检测其他类型的时候,都返回 object
,不太稳定。
补充:
typeof NaN; // number
typeof 10n; // bigint
typeof Symbol(); // symbol
NaN
返回为number
10n
是BigInt
的表示方法,在数字后面追加n
表示BigInt
,或者BigInt(10)
也是一样的。Symbol()
就是Symbol
6.2 instanceof
返回目录
instanceof
判断原型链指向,我们可以看一下它的实现原理:
function instanceOf(a, b) {
// 通过 typeof 判断基本类型
if (typeof a !== 'object' || b === null) {
return false;
}
// getPrototypeOf 是 Object 自带的一个方法
// 可以拿到参数的原型对象
let proto = Object.getPrototypeOf(a);
const prototype = b.prototype;
// 从当前 __proto__ 开始查找
while (proto) {
// 如果找到 null 还没有找到,返回 false
if (proto === null) {
return false;
}
// 如果 a.__proto__.xxx === b.prototype,返回 true
if (proto === prototype) {
return true;
}
// 进一步迭代
proto = proto.__proto__;
}
}
instanceof
就是原型链的查找过程。
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = { name: 'jsliang', age: 25 };
var func = function () { console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error = new Error();
console.log(
num instanceof Number, // false
str instanceof String, // false
bool instanceof Boolean, // false
und instanceof Object, // false
nul instanceof Object, // false
arr instanceof Array, // true
json instanceof Object, // true
func instanceof Function, // true
date instanceof Date, // true
reg instanceof RegExp, // true
error instanceof Error, // true
)
保险起见,instanceof
能判断的有:
Array
Function
Date
RegExp
Error
其他的不太能确定。
6.3 constructor
返回目录
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = { name: 'jsliang', age: 25 };
var func = function () { console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error = new Error();
function Person(){
}
var Tom = new Person();
console.log(
Tom.constructor === Person,
num.constructor === Number,
str.constructor === String,
bool.constructor === Boolean,
arr.constructor === Array,
json.constructor === Object,
func.constructor === Function,
date.constructor === Date,
reg.constructor === RegExp,
error.constructor === Error
);
得到的所有结果都是 true
,除了 undefined
和 null
,其他类型基本可以通过 constructor
判断。
不过因为 constructor
的属性是可以被修改的,可能导致检测出的结果不正确。
6.4 Array.prototype.toString.call
返回目录
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = { name: 'jsliang', age: 25 };
var func = function () { console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error = new Error();
console.log(
Object.prototype.toString.call(num), // [object Number]
Object.prototype.toString.call(str), // [object String]
Object.prototype.toString.call(bool), // [object Boolean]
Object.prototype.toString.call(arr), // [object Array]
Object.prototype.toString.call(json), // [object Object]
Object.prototype.toString.call(func), // [object Function]
Object.prototype.toString.call(und), // [object Undefined]
Object.prototype.toString.call(nul), // [object Null]
Object.prototype.toString.call(date), // [object Date]
Object.prototype.toString.call(reg), // [object RegExp]
Object.prototype.toString.call(error), // [object Error]
);
一个完美的判断方法,可以检测上面提到的所有类型,只需要将它的结果 result.slice(8, -1)
就能得到具体的类型。
七 参考文献
返回目录
- [x] JavaScript: 变量提升和函数提升【阅读建议:10min】
- [x] 变量提升必须注意的问题【阅读建议:5min】
- [x] 笔试题-Js前端变量提升的问题【阅读建议:5min】
- [x] 关于JS变量提升的一些坑【阅读建议:5min】
- [x] JS 中对变量类型的判断【阅读建议:10min】
- [x] js中块级作用域以及函数作用域之间有什么区别?(代码解析)【阅读建议:5min】
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。