本文字符数9600+,阅读时间约20分钟。
一、let命令
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for(let i = 0; i < arr.length; i++){}
console.log(i)//ReferenceError: i is not defined
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
console.log(foo); // ReferenceError
let foo = 2;
typeof x; // ReferenceError
let x;
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
二、const命令
const的定义是不可重新赋值的值,与不可变的值(immutable value)不同。重新赋值会报错。
如果const定义的是引用类型,比如Object,在定义之后仍可以修改属性。
它的使用场景很广,包括常量、配置项以及引用的组件、定义的 “大部分” 中间变量等,都应该以const做定义。
let多用于在 loop循环(for,while)和少量必须重赋值的变量上。
const PI = 3.1415;
PI // 3.1415
PI = 3;// TypeError: "PI" is read-only
const foo;// SyntaxError: missing = in const declaration
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;}
var message = "Hello!";let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
const foo = {};
foo.prop = 123;
foo.prop// 123
foo = {} // TypeError: "foo" is read-only不起作用
const a = [];
a.push("Hello"); // 可执行
a.length = 0; // 可执行
a = ["Dave"]; // 报错
var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1
let b = 1;
window.b // undefined
const foo = Object.freeze({});
foo.prop = 123; // 不起作用
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, value) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
};
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
06、在函数内部,没有var声明的变量,是作为全局变量存在的;
三、变量+标识符
var test = "hi";
如果未在var声明语句中给变量指定初始值,那么它的初始值就是undefined。var test;
var test1 = "hi", test2 = "hello";
var test = "hi", age = 25;
var test = "hi";
alert(test);
test = 55;
alert(test);
var truevar = 1; // 声明一个不可删除的全局变量
fakevar = 2; // 创建全局对象的一个可删除的属性
this.fakevar2 = 3; // 同上
delete truevar // => false: 变量并没有被删除
delete fakevar // => true: 变量被删除
delete this.fakevar2 // => true: 变量被删除
声明: 变量将会对一个包含相应作用域范围(比如: 在一个函数里面)的名字进行注册。
初始化: 当你声明一个变量的时候会自动的进行初始化,也就是说由 JavaScript 解释引擎为变量分配了一块内存。
赋值: 把一个指定的值指派(赋)给一个变量。
四、变量作用域
在函数外声明的变量是全局变量,网页上的所有脚本和函数都能访问它。
在JS函数内部声明的变量(使用 var)是局部变量,只能在函数内部访问它。(该变量的作用域是局部的)。 可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。 只要函数运行完毕,局部变量就会被删除。
- 在任何地方不使用 var ,let,const声明变量。
- 直接向未声明的变量赋值。
- 在函数外部使用 var 声明的变量。
- 以 window. variable 形式声明的变量。
- 第三方的功能库文件。
- 同事的代码。
- 第三方的用户分析代码。
01、函数内部不使用 var 声明变量
function sum(x, y) {
// 不推荐写法: 隐式全局变量
result = x + y;
return result;
}
02、使用任务链进行部分 var 声明
// 反例,勿使用
function foo() {
var a = b = 0;
// ...
}
var scope = "global"; // 声明一个全局变量
function checkscope() {
var scope = "local"; // 声明一个同名的局部变量
return scope; // 返回局部变量的值,而不是全局变量的值
}
checkscope() // => "local"
var scope = "global scope"; // 全局变量
function checkscope() {
var scope = "local scope"; //局部变量
function nested() {
var scope = "nested scope"; // 嵌套作用域内的局部变量
return scope; // 返回当前作用域内的值
}
return nested();
}
checkscope() // => "嵌套作用域"
var a = 2;
function test(){
console.log(a);
var a = 10;
}
test();//undefined;
01、在程序设计语言中,变量可分为自由变量与约束变量两种。
简单来说,局部变量和参数都被认为是约束变量;
而不是约束变量的则是自由变量。
02、在冯·诺依曼计算机体系结构的内存中,变量的属性可以视为一个六元组:(名字,地址,值,类型,生命期,作用域)。
地址属性具有明显的冯·诺依曼体系结构的色彩,代表变量所关联的存储器地址(内存)。
类型规定了变量的取值范围和可能的操作。(比如数值有取值范围)
生命期表示变量与某个存储区地址绑定的过程。
根据生命期的不同,变量可以被分为四类:静态、栈动态、显式堆动态和隐式堆动态。
作用域限制了变量在语句中的适用范围,分为词法作用域和动态作用域两种。
在词法作用域的环境中,变量的作用域与其在代码中所处的位置有关。
由于代码可以静态决定(运行前就可以决定),所以变量的作用域也可以被静态决定,因此也将该作用域称为静态作用域。
在动态作用域的环境中,变量的作用域与代码的执行顺序有关。
JavaScript和C都是词法作用域语言。
五、块级作用域
01、内层变量覆盖外层变量,因为存在变量声明提升和函数声明提升。
函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
var tmp = new Date();
function f(){
console.log(tmp);
if (false){
var tmp = "hello world";
}
}
f() // undefined
02、用来计数的循环变量泄露为全局变量。循环结束后,它并没有消失,泄露成了全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i]);
}
console.log(i); // 5
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
{{{{let insane = 'Hello World';{let insane = 'Hello World';}}}}};
// IIFE写法
(function () {var tmp = ...;...}());
// 块级作用域写法
{let tmp = ...;...}
function f() {
console.log('I am outside!');
}
(function () {
if (false) { // 重复声明一次函数f
function f() {
console.log('I am inside!');
}
}
f();
}());
{
let a = 'secret';
function f() {
return a;
}
}
f() // 报错
let f;{let a = 'secret';
f = function () {return a;}}f(); // "secret"
{{{{{let insane = 'Hello World'}}}}};
{{{{{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
六、变量和函数声明提升
【01】变量提升(hoisting)是 JavaScript 编译器的行为,将所有的变量和函数声明移至当前作用域的最高处。
然而,只有声明被提升了。任何赋值行为都被留在它们所在的地方。
如果在声明之前访问该变量,它的值是undefined。
例子:
(function() {
var foo = 1;
var bar = 2;
var baz = 3;
console.log(foo + " " + bar + " " + baz);//"1 2 3"
})();
【】例子:
(function() {
var foo = 1;
console.log(foo + " " + bar + " " + baz);// "1 undefined undefined"
var bar = 2;
var baz = 3;
})();
【】例子:
(function() {
var foo = 1;
console.log(foo + " " + bar + " " + baz);// ReferenceError baz未定义。
var bar = 2;
函数声明也是可以被提升的。
函数声明表达式不会声明提升。
【】例子:
foo();//Hello! 成功。
function foo() {
console.log("Hello!");
}
【】例子:
foo();//失败。函数表达式声明不能函数提升。
var foo = function() {
console.log("Hello!");
};
function double(num) {
console.log(myVariable); // => undefined
var myVariable;
return num * 2;
}
double(3); // => 6