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,而且还支持异步加载后再执行呢?所以才有了CMDCMD(依赖就近)
由国内的玉伯提出,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高级程序设计
看书