GoMind Hub

Javascript

计算机知识思维导图:Javascript。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。

2026-08-25

计算机知识学习资料思维导图
## Javascript
### Javascript
#### 对象
##### 六种主要类型
##### 属性描述符
##### 遍历
##### 对象拷贝
#### var
##### 其创建的全局变量无法删除(delete)
##### 无var创建的隐式全局变量可删除(delete)
#### 花括号的位置
##### 分号插入机制(semicolon insertion mechanism)
#### 原型链
##### 原型 prototype
#### 作用域
##### 异常
##### 函数作用域
##### 块作用域
#### 函数
##### 函数参数
##### 命名函数表达式
##### 隐式转换
##### 函数式编程
#### 变量
##### NaN
##### 变量声明被提前(作用域范围内)
##### null 与 undefined
#### 闭包
##### 用处
##### 注意
#### 改变环境上下文
##### apply/call
#### 声明提升
##### 编译器执行流程
##### 函数及变量声明都会被提升
##### 函数声明优先被提升
##### 被条件判断所控制的函数声明
#### 模块化规范
##### CommonJS
##### AMD(依赖前置)
##### CMD(依赖就近)
##### 为兼容多种规范,可以将类库代码包装在一个闭包中
#### this
##### 绑定规则
#### Event loop(事件循环)
##### 同步任务
##### 异步任务
#### ECMAScript
##### 版本
##### 规范提交流程
#### 书籍
##### 你不知道的Javascript(上中下)
##### JavaScript高级程序设计

## JavaSscript
### JavaSscript
#### 看书
##### JavaScript高级程序设计

> 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。

Javascript

Javascript
string
number
boolean
null
typeof(null) === object
undefined
object
六种主要类型
writable
enumerable
configurable
value
属性描述符
some
every
forEach
for ... in
for ... of
for...of是es6引入的新遍历方式,使用的是迭代器,其原理相当于:



var myObject = {

			a: 2,

			b: 3

		};

	Object.defineProperty( myObject, Symbol.iterator, {

		enumerable: false,

		writable: false,

		configurable: true,

		value: function() {

			var o = this;

			var idx = 0;

			var ks = Object.keys( o );

			return {

				next: function() {

					return {

					value: o[ks[idx++]],

					done: (idx > ks.length)

					};

				}

			};

		}

	} );

	// 手动遍历 myObject

	var it = myObject[Symbol.iterator]();

	var x = it.next();

	while(!x.done){

	    console.log(x.value);;

		x = it.next();

	}
遍历
JSON.parse(JSON.stringify())
递归复制所有层级属性
$.extend
深拷贝
浅拷贝
Object.assign()仅对最外一层做了深拷贝,里面的对象仍然是浅拷贝
Object.assign()仅对最外一层做了深拷贝,里面的对象仍然是浅拷贝
'use strict';



  let obj1 = { a: 0 , b: { c: 0, d:{ee: 323 }}};

  let obj2 = Object.assign({}, obj1);

  console.log(JSON.stringify(obj2)); 



  // 深拷贝

  obj1.a = 1;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2)); 

  

  // 深拷贝

  obj2.a = 2;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2)); 



  // 以下两个例子标明除了最外一层,里面的对象都是浅拷贝

  obj1.b.c = 3;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2)); 

  obj2.b.c = 5;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2)); 

  

  obj1.b.d.ee = 988;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2)); 

  obj2.b.d.ee = 123;

  console.log(JSON.stringify(obj1)); 

  console.log(JSON.stringify(obj2));
对象拷贝
对象
其创建的全局变量无法删除(delete)
无var创建的隐式全局变量可删除(delete)
var
分号插入机制(semicolon insertion mechanism)
花括号的位置
原型 prototype
function Person(){
}
Person.prototype = {
 name : "Nicholas",
 age : 29,
 job: "Software Engineer",
 sayName : function () {
 alert(this.name);
 }
}; 
此时constructor 属性不再指向 Person,而是指向object : 

var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true 

若构造函数很重要则可写成 : 
( 此时constructor 的 Enumerable 会被设置为true )

Person.prototype = {
 constructor : Person, 
 name : "Nicholas",
 age : 29,
 job: "Software Engineer",
 sayName : function () {
 alert(this.name);
 }
}; 

默认情况下,原生的 constructor 属性是不可枚举的,因此如果你使用兼容 ECMAScript 5 的 JavaScript 引擎,可以试一试Object.defineProperty() : 

//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
 enumerable: false,
 value: Person
});
原型链
ReferenceError
在作用域中找不到相应变量就会提示该错误
TypeError
变量已声明,但未赋值,导致执行相应操作报错,

如 var a ;  a(12); 

变量a仅做了声明,但是里面被当做函数来执行
异常
function foo(a) {

    var b;

	console.log( a + b );

	b = a;

  }

  foo( 2 );



这是一个“未声明”的变量,因为在任何相关的作用域中都无法找到它。在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常
匿名与具名
解决 undefined 标识符的默认值被错误覆盖导致的异常
将一个参数命名为 undefined,但是在对应的位置不传入任何值,这样就

可以保证在代码块中 undefined 标识符的值真的是 undefined:





undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做!

(function IIFE( undefined ) {

  var a;

  if (a === undefined) {

     console.log( "Undefined is safe here!" );

   }

})();
倒置代码的运行顺序
var a = 2;30 

(function IIFE( def ) {

  def( window );

})(function def( global ) {

   var a = 3;

   console.log( a ); // 3

   console.log( global.a ); // 2

});
立即执行函数 IIFE
函数作用域
垃圾收集
function process(data) {

 // 在这里做点有趣的事情

}

var someReallyBigData = { .. };

process( someReallyBigData );

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt) {

      console.log("button clicked");

}, /*capturingPhase=*/false );



click 函数的点击回调并不需要 someReallyBigData 变量。理论上这意味着当 process(..) 执

行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于 click 函数形成

了一个覆盖整个作用域的闭包, JavaScript 引擎极有可能依然保存着这个结构(取决于具体

实现)。块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:

function process(data) {

 // 在这里做点有趣的事情

}

// 在这个块中定义的内容完事可以销毁!

{

 let someReallyBigData = { .. };

 process( someReallyBigData );

}

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt){

 console.log("button clicked");

}, /*capturingPhase=*/false );
块作用域
作用域
所有函数的参数都是按值传递的
Example:



function setName(obj) {

 obj.name = "Nicholas";

 obj = new Object();

 obj.name = "Greg";

}

var person = new Object();

setName(person);

alert(person.name); //"Nicholas" 



参数按值传递,执行setName函数时会将person所指向的Object实例地址复制一份,传到参数内部。该副本及其person所指向的都是同一个内存区域,即他们存储的指针地址都一样。
函数参数
声明
表达式
命名函数表达式
当我们没有重新定义toString与valueOf时,函数的隐式转换会调用默认的toString方法,它会将函数的定义内容作为字符串返回。而当我们主动定义了toString/vauleOf方法时,那么隐式转换的返回结果则由我们自己控制了。其中valueOf的优先级会toString高一点。
隐式转换
栗子1: 

function fn() {

    return 20;

}



console.log(fn + 10); 



栗子2: 

function fn() {

    return 20;

}



fn.toString = function() {

    return 10;

}



console.log(fn + 10);  // 输出结果是多少?



栗子3:



function fn() {

    return 20;

}



fn.toString = function() {

    return 10;

}



fn.valueOf = function() {

    return 5;

}



console.log(fn + 10);
多参数的函数转换成单参数的形式
function currying(fn, n) {

  return function (m) {

    return fn.call(this, m, n);

  };

}
柯里化
函数式编程
函数
NaN
console.log(NaN == NaN);    // false
console.log(NaN === NaN);  // false

alert(isNaN(true)); //false(可以被转换成数值 1)
变量声明被提前(作用域范围内)
null 与 undefined
1. null 不是空对象指针,而是基本数据类型,故有:

  typeof(null)  // object



在 JavaScript 中二进制前三位都为 0 的话会被判

断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“object”



2.  undefined 派生自 null

   console.log(null == undefined);  //true 



    console.log(null === undefined);  //false
变量
读取函数内部变量
让这些变量始终保持在内存中
栗子1:



  function f1(){

    var n=999;



    nAdd=function(){n+=1}



    function f2(){

      alert(n);

    }

    return f2;

  }



  var result=f1();



  result(); // 999



  nAdd();



  result(); // 1000



原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。



栗子2: 



for( var i = 0; i < 5; i++ ) {

	setTimeout(() => {

		console.log( i );

	}, 1000 * i)

}

// 结果为5个5,每秒输出一个



根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,

但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。



栗子3: 

for (var i = 1; i <= 5; i++) {

    let j = i;  // 输出结果正常

    setTimeout(function timer() {

        console.log(j);

    },j*1000);

}



栗子4:



for (var i = 1; i <= 5; i++) {

    var j = i; // 输出结果仍然是5个5

    setTimeout(function timer() {

        console.log(j);

    },j*1000);

}



栗子5:

for( var i = 0; i < 5; i++ ) {

	((j) => {

		setTimeout(() => {

			console.log( j );

		}, 1000 * j)

	})(i)	

}



栗子6: 



function test(){

     for (let i=0; i<5; i++) {  // 正常

        setTimeout( function timer() {

            console.log(new Date(),i);

        }, i*1000 );

    }

 //   console.log("end",new Date(),i);  //因为变量作用域的问题,这里会报i 不存在,未声明    

}
用处
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大, 所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。 解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
注意
闭包
闭包就是能够读取其他函数内部变量的函数。



栗子1: 

    getNameFunc返回的匿名函数的执行环境是全局的,而且this只在函数内部起作用。此时的this.name在匿名函数中找不到,所以就从全局中找,找到后打印出来。

   var name = "The Window";



  var object = {

    name : "My Object",

    getNameFunc : function(){

      return function(){

        return this.name;

      };

    }

  };



  alert(object.getNameFunc()());



栗子2:



     var name = "The Window";

  var object = {

    name : "My Object",

    getNameFunc : function(){

      var that = this;

      return function(){

        return that.name;

      };

    }

  };



  alert(object.getNameFunc()());
apply
func.apply(this, [arg1, arg2])
call
func.call(this, arg1, arg2);
bind
绑定后返回一个函数,待后续调用。

多次 bind() 是无效的,更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
绑定后立即执行
绑定后非立即执行
apply/call
都是为了改变函数执行上下文



1. 改变this指针,第一个参数都是this要指向的对象,也就是想指定的上下文;



2. 借用其他对象的方法





下面就借用一道面试题,来更深入的去理解下 apply 和 call 。



定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:



function log(msg) {

  console.log(msg);

}

log(1);    //1

log(1,2);    //1

上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:



function log(){

  console.log.apply(console, arguments);

};

log(1);    //1

log(1,2);    //1 2

接下来的要求是给每一个 log 消息添加一个"(app)"的前辍,比如:



log("hello world");    //(app)hello world

该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift,像这样:





function log(){

  var args = Array.prototype.slice.call(arguments);

  args.unshift('(app)');



  console.log.apply(console, args);

};
改变环境上下文
1. 编译阶段,定义声明
2. 执行阶段,保留原地
编译器执行流程
函数表达式不会被提升
函数声明会被提升,但是函数表达式却不会被提升。

foo(); // 不是 ReferenceError, 而是 TypeError!

var foo = function bar() {

// ...

};



即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:

foo(); // TypeError

bar(); // ReferenceError

var foo = function bar() {

// ...

};
使用let定义的变量不会被提升
函数及变量声明都会被提升
foo();

function foo() {

   console.log( a ); // undefined

   var a = 2;

}



函数声明会被提升,但是函数表达式却不会被提升。

foo(); // 不是 ReferenceError, 而是 TypeError!

var foo = function bar() {

// ...

};
函数声明优先被提升
foo(); // 1



var foo;

function foo() {

   console.log( 1 );

}

foo = function() {

    console.log( 2 );

};



会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:

function foo() {

   console.log( 1 );

}

foo(); // 1

foo = function() {

   console.log( 2 );

};



注意, var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽

略了),因为函数声明会被提升到普通变量之前。
被条件判断所控制的函数声明
foo(); // "b"

var a = true;

if (a) {

   function foo() { console.log("a"); }

}

else {

   function foo() { console.log("b"); }

}
声明提升
没有模块系统
标准库较少
没有标准接口
缺乏包管理系统
缺陷
CommonJS
以同步方式加载模块,主要用在后端。



var clock = require('clock');

clock.start();

这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。但是如果在客户端,加载模块的时候有可能出现“假死”状况。
AMD(依赖前置)
异步的加载模块,Asynchronous Module Definition:



require(['clock'],function(clock){

  clock.start();

});



AMD虽然实现了异步加载,但是开始就把所有依赖写出来是不符合书写的逻辑顺序的,能不能像commonJS那样用的时候再require,而且还支持异步加载后再执行呢?所以才有了CMD
CMD(依赖就近)
由国内的玉伯提出,CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的:

define(function(require, exports, module) {

   var clock = require('clock');

   clock.start();

});



AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
为兼容多种规范,可以将类库代码包装在一个闭包中
模块化规范
默认绑定
隐式绑定
function foo() {

console.log( this.a );

}

var obj = {

a: 2,

foo: foo

};

obj.foo(); // 2







function foo() {

console.log( this.a );

}

var obj2 = {

a: 42,

foo: foo

};

var obj1 = {

a: 2,

obj2: obj2

};

obj1.obj2.foo(); // 42



隐式绑定丢失,退回默认板顶的栗子1:



function foo() {

console.log( this.a );

}

var obj = {

a: 2,

foo: foo

};

var bar = obj.foo; // 函数别名!

var a = "oops, global"; // a 是全局对象的属性

bar(); // "oops, global"





栗子2: 



function foo() {

console.log( this.a );

}

function doFoo(fn) {

// fn 其实引用的是 foo

fn(); // <-- 调用位置!

}

var obj = {

a: 2,

foo: foo

};

var a = "oops, global"; // a 是全局对象的属性

doFoo( obj.foo ); // "oops, global"





栗子3: 



function foo() {

console.log( this.a );

}

var obj = {

a: 2,

foo: foo

};

var a = "oops, global"; // a 是全局对象的属性

setTimeout( obj.foo, 100 ); // "oops, global"
显式绑定
new绑定
1. 函数中是否使用new调用,若是则this绑定的是新创建的对象
2. 函数是否通过call、apply或硬绑定调用,若是则this绑定的是指定的对象
3. 函数是否在某个上下文对象中调用,若是则this绑定的是该上下文对象
4. 以下都是则使用默认绑定
1. 将null或undefined作为绑定对象传入call,apply或者bind,实际应用的是默认绑定
function foo() {

console.log( this.a );

}

var a = 2;

foo.call( null ); // 2
间接引用函数会应用默认绑定规则
function foo() {

console.log( this.a );

}98 | 第 2 章

var a = 2;

var o = { a: 3, foo: foo };

var p = { a: 4 };

o.foo(); // 3

(p.foo = o.foo)(); // 2
特例
优先级
绑定规则
this
同步任务
Promise
process.nextTick
MutationObserver
微观任务
setTimeout
setInterval
setImmediate
UI rendering
I/O
宏观任务
异步任务
Event loop(事件循环)
函数体内的this对象是定义时所在的对象,而不是使用时所在的对象
// ES6 箭头函数

function foo() {

  setTimeout(() => {

    console.log('id:', this.id);

  }, 100);

}



// ES5 中对应的写法为

function foo() {

  var _this = this;



  setTimeout(function () {

    console.log('id:', _this.id);

  }, 100);

}



栗子1:



function foo() {

  setTimeout(() => {

    console.log('id:', this.id);

  }, 100);

}



var id = 21;

foo.call({ id: 42 });  // id: 42



上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。



栗子2: 下面有几个this对象



function foo() {

  return () => {

    return () => {

      return () => {

        console.log('id:', this.id);

      };

    };

  };

}



var f = foo.call({id: 1});



var t1 = f.call({id: 2})()(); // id: 1

var t2 = f().call({id: 3})(); // id: 1

var t3 = f()().call({id: 4}); // id: 1
不可当做构造函数,即不可以使用new命令,否则报错
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
不可以使用arguments对象,该对象在函数体内不存在。 如果要用,可以用 rest 参数代替。
不可以使用yield命令,因此箭头函数不能用作 Generator 函数
无法使用apply, class, bind
箭头函数 =>
箭头函数转成 ES5 的代码如下。



// ES6

function foo() {

  setTimeout(() => {

    console.log('id:', this.id);

  }, 100);

}



// ES5

function foo() {

  var _this = this;



  setTimeout(function () {

    console.log('id:', _this.id);

  }, 100);

}
参数默认值
rest参数
rest参数之后不能再有其他参数,否则报错



function add(...values) {

  let sum = 0;



  for (var val of values) {

    sum += val;

  }



  return sum;

}



add(2, 5, 3) // 10
函数内部严格模式
name属性
:: 双冒号运算符
双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。



如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
尾调用
即某个函数的最后一步是调用另一个函数。



尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
一般实现,复杂度为O(N*lgN)
function Fibonacci (n) {

  if ( n <= 1 ) {return 1};



  return Fibonacci(n - 1) + Fibonacci(n - 2);

}



Fibonacci(10) // 89

Fibonacci(100) // 堆栈溢出

Fibonacci(500) // 堆栈溢出
优化后的实现
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {

  if( n <= 1 ) {return ac2};



  return Fibonacci2 (n - 1, ac2, ac1 + ac2);

}



Fibonacci2(100) // 573147844013817200000

Fibonacci2(1000) // 7.0330367711422765e+208

Fibonacci2(10000) // Infinity
时间复杂度为O(n),空间复杂度为O(1)
function fibonacci(n){  

    var a,b,res;  

    a = b = 1;  

    if(n == 0){  

        return 0;  

    }  

    if(n == 1 || n == 2){  

        return 1;  

    }   

    for(var i=2;i<n;i++){  

        res = a + b;  

        a = b;  

        b = res;  

    }  

    return res;  

}
时间复杂度为O(n),空间复杂度为O(n)
function fibo(x){

    var arr = new Array(x);

	

	arr[0] = 1;

	arr[1] = 1;

	for(var index=2;index<x;index++){

	   arr[index] = arr[index-1]+arr[index-2];

	}

	

	var result = 0;

	arr.forEach((item)=>{

	   result+=item;

	});

	return result;

  }



  fibo(5);
引申其他非递归实现:
斐波那契数 Fibonacci
阶乘
function tailFactorial(n, total) {

  if (n === 1) return total;

  return tailFactorial(n - 1, n * total);

}



function factorial(n) {

  return tailFactorial(n, 1);

}



factorial(5) // 120
一般实现
function sum(x, y) {

  if (y > 0) {

    return sum(x + 1, y - 1);

  } else {

    return x;

  }

}



sum(1, 100000)
优化后
function tco(f) {

  var value;

  var active = false;

  var accumulated = [];



  return function accumulator() {

    accumulated.push(arguments);

    if (!active) {

      active = true;

      while (accumulated.length) {

        value = f.apply(this, accumulated.shift());

      }

      active = false;

      return value;

    }

  };

}



var sum = tco(function(x, y) {

  if (y > 0) {

    return sum(x + 1, y - 1)

  }

  else {

    return x

  }

});



sum(1, 100000)
简单累加,每次加一
递归函数的改写
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。



非尾递归的 Fibonacci 数列实现如下。



function Fibonacci (n) {

  if ( n <= 1 ) {return 1};



  return Fibonacci(n - 1) + Fibonacci(n - 2);

}



Fibonacci(10) // 89

Fibonacci(100) // 堆栈溢出

Fibonacci(500) // 堆栈溢出



尾递归优化过的 Fibonacci 数列实现如下。



function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {

  if( n <= 1 ) {return ac2};



  return Fibonacci2 (n - 1, ac2, ac1 + ac2);

}



Fibonacci2(100) // 573147844013817200000

Fibonacci2(1000) // 7.0330367711422765e+208
尾调用优化
即减少调用栈,防止栈溢出stackoverflow
函数参数尾逗号
函数扩展
仅在代码块内有效
栗子1: 



for (let i = 0; i < 10; i++) {

  // ...

}



console.log(i);

// ReferenceError: i is not defined



栗子2: 

尽管循环中的匿名函数是在各个迭代中分别定义的,

但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。



var a = [];

for (var i = 0; i < 10; i++) {

  a[i] = function () {

    console.log(i);

  };

}

a[6](); // 10



如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。因为每一次循环的i其实都是一个新的变量,这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。



var a = [];

for (let i = 0; i < 10; i++) {

  a[i] = function () {

    console.log(i);

  };

}

a[6](); // 6
不存在变量提升的行为
// var 的情况

console.log(foo); // 输出undefined

var foo = 2;



// let 的情况

console.log(bar); // 报错ReferenceError

let bar = 2;
特例
作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。



typeof undeclared_variable // "undefined"
暂时性死区
即变量未声明前无法对其进行操作。





function bar(x = y, y = 2) {

  return [x, y];

}



bar(); // 报错





var tmp = 123;



if (true) {

  tmp = 'abc'; // ReferenceError

  let tmp;

}
不允许在相同作用域内重复声明
引申for作用域,即就是设置循环变量的那部分是一个父作用域, 而循环体内部是一个单独的子作用域。 for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); // 输出三次abc } 因为如果在同一个代码块中使用let同时定义相同的两个变量时会提示错误: Uncaught SyntaxError: Identifier 'a' has already been declared
let
let与const
类
模块
Promise
模板字符串
解构赋值
ES 6
Ecmascript 2015
Array.prototype.includes
指数操作符 **
ES 7
Ecmascript 2016
async/await
Object.values()
Object.values()是一个与Object.keys()类似的新函数,但返回的是Object自身属性的所有值,不包括继承的值。
Object.entries()
for(let [key,value] of Object.entries(obj1)){

	console.log(`key: ${key} value:${value}`)

}
String padding
在ES8中String新增了两个实例函数String.prototype.padStart和String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾
Object.getOwnPropertyDescriptors()
函数参数列表结尾允许逗号
ES 8
Ecmascript 2017
异步迭代
Rest/Spread 属性
正则表达式命名捕获组
const reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;

match  = reDate.exec('2018-04-30');

year  = match.groups.year;

month = match.groups.month;

day = match.groups.day;
正则表达式反向断言
ES 9
ES 10
版本
stage0: 想法提交阶段
stage1:提案阶段
stage2:草案阶段
stage3:候选阶段
stage4:完成阶段
规范提交流程
ECMAScript
你不知道的Javascript(上中下)
JavaScript高级程序设计
书籍

JavaSscript

JavaSscript
JavaScript高级程序设计
看书