ES5和ES6的语法规范

71 阅读5分钟

es5的语法规范

strict模式

严格模式限制一些用法,'use strict',写在程序开始,限制所有程序
  • 在严格模式中,形参不允许同名
function (val, val) {}
  • 在严格模式中,变量要先声明在赋值
var b = 2;
console.log(val);
  • 普通函数this指向的是window, 但是如果是严格模式this是undefined
fn(12)
this  // 在事件函数中,this指向事件原
  • 定义变量必须要有var
  • 严格模式下八进制被禁用
  • 严格模式下,函数必需声明在程序的最前端
  • 严格模式下,构造函数不可以直接调用
  • 严格模式下,不可以使用delete删除已经声明的变量
  • 严格模式下,argument不同
    在严格模式下,形参和arguments对象是完全独立的。
    在非严格模式下,修改形参的值也会反映到agruments对象中
// 开启严格模式
'use strict'
function fun(value) {
    var value = '猪猪侠';
    console.log(value); // 猪猪侠 -> 就近原则
    console.log(arguments[0]);
}
fun ('超人强'); // 非严格模式下,agruments对象获取的值与形参有关

在严格模式下,使用arguments对象的callee()方法,结果抛出异常。
在非严格模式下,使用arguments对象的callee()方法,表示调用函数本身

'use strict'
function fun () {
    console.log(arguments.length);
    // 在严格模式下无法调用arguments对象的callee()方法
    console.log(arguments.callee)
}
fun()
  • 严格模式下,eval()解析后有自己的作用域

Array增加方法

增加了every,some,forEach,filter,map,reduce,reduceRight,indexof,lastIndexof,isArray方法。

Object方法

  • Object.getPrototypeOf

语法:

Object.getPrototypeOf(obj);   // 返回指定对象的原型
  • Object.create

语法:

/**
*参数说明:
*@proto: 新创建对象的原型对象
*@propertiesObject: 可选。需要传入一个对象
*/
Object.create(proto,[propertiesObject]); // 创建一个新对象
  • Object.getOwnPropertyNames

语法

Object.getOwnPropertyNames(obj) // 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
  • Object.defineProperty

语法:

/**
*参数说明:
*obj: 要定义属性的对象
*prop: 要定义或修改的属性的名称或Symbol
*descriptor: 要定义或修改的属性描述符
*/
Object.defineProperty(obj, prop, descriptor); //直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • Object.getOwnPropertyDescriptor

语法:

/**
*参数说明:
*obj: 需要查找的目标对象
*prop: 目标对象内属性名称
*/
Object.getOwnPropertyDescriptor(obj, prop)  // 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
  • Object.defineProperties

语法:

/**
*参数说明:
*obj: 需要在其上定义或修改属性的对象
*prop: 要定义其可枚举属性或修改的属性描述符的对象
*/
Object.defineProperties(obj, props);  // 直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
  • Object.keys

语法:

Object.keys(obj);  //返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回顺序一致。
  • Object.preventExtensions / Object.isExtensible

语法:

Object.preventExtensions(obj);  //让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.isExtensible(obj); // 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)
  • Object.seal / Object.isSealed

语法:

Object.seal(obj);  //封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。

Object.isSealed(obj);  // 判断一个对象是否被密封。
  • Object.freeze / Object.isFrozen

语法:

Object.freeze(obj);   // 可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

Object.isFrozen(obj); // 判断一个对象是否被冻结。

特殊说明:

Object.assign(target,source1,source2,...)
该方法主要用于对象的合并,将源对象source的所有可枚举属性合并到目标对象target上,此方法只拷贝源对象的自身属性,不拷贝继承的属性。
该方法合并的操作是浅拷贝,并不是深拷贝,他只能对值进行复制。特别要注意的是,该方法处理数组时,会把数组当成对象来处理

Object.assign([1, 2, 3], [4, 5]) // 把数组当作对象来处理
//结果 [4, 5, 3]

es6语法

ECMAScript6在保证向下兼容的前提下,提供大量新特性

块级作用域关键字let,常量const

let 声明变量,不允许重复声明,必须在某个{}中使用,在作用域范围没有变量提升,暂时性死区:通过let声明的变量声明之前该变量不可用,这种现象就称作暂时性死区
const定义常量(不会变化的量,以固定值的形式存在,使用const定义数组和对象,可以对数组和对象的元素或属性进行操作️)

对象字面量的属性赋值简写(property value shorthand)

let obj = {
    // __proto__
    __proto__: theProtoObj,
    // Shorthand for `handler: handler`
    handler,
    // Method definitions
    toString() {
        // Super calls
        return "d" + super.toString();
    },
    // Computed (dynamic) property names
    ['prop_' + (() => 42) ()] : 42
}

赋值解构

let singer = { first: "Bob", last: "Dylan"};
let { first: f, last: 1 } = singer;  // 相当于 f = "Bob", 1 = "Dylan"
let [all, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec("2015-10-25")
let [x, y] = [1, 2, 3];   // x = 1, y = 2

函数参数-默认值、参数打包、数组展开(Default、Rest、Spread)

// 函数参数-默认值
function findArtist(name='lu', age="26"){
    
}
// 参数打包
function f(x, ...y) {
    // y is an Array
    return x * y.length;
}
f(3, "hello", true) == 6
//数组展开
function f(x, y, z) {
    return x + y + z;
}
// Pass each elem of array as argument
f(...[1, 2, 3]) == 6;

箭头函数 Arrow functions

- 简化了带啊形式,默认return表达式结果
- 自动绑定语义this,即定义函数时的this。如上面例子中,forEach的匿名函数参数中用到的this

字符串模版 Template strings

var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// return "Hello Bob, how are you today?"

Iterators(迭代器) + for...of

迭代器有个next方法,调用返回:

  • 返回迭代对象的一个元素:{done: false, value:elem}
  • 如果已达到迭代对象的末端: {done: true, value: retVal}
for (var n of ['a', 'b', 'c']) {
    console.log(n);
}
// 打印a, b, c

生成器(Generators)

语法:
function *    // 创建generator函数,generator函数用于创建懒迭代器

调用generator函数时会返回一个generator对象。generator对象遵循迭代器接口,即通常所见到的next、return和throw函数。

function* infiniteList() {
    let i = 0;
    while (i < 3) {
        yield i++;
    }
}

let item = infiniteList();
console.log(item.next());  // { value: 0, done: false }
console.log(item.next());  // { value: 1, done: false }
console.log(item.next());  // { value: 2, done: false }
console.log(item.next());  // { value: undefined, done: true }

它允许一个函数可以暂停执行,比如当执行了第一次的item.next()后,可以先去做别的事情,然后再回来继续执行item.next().
需要注意的是:直接调用generator函数,并不会执行,只会创建一个generator对象

function* generator() {
    console.log('我先喝口水,等我回来');
    yield 0;
    console.log('我回来了游戏继续');
    yield 1;
    console.log('游戏结束')
}
let item = generator();
console.log(item.next());   // { value: 0, done: false }
console.log(item.next());   // { value: 1, done: false }
console.log(item.next());   // { value: undefined, done: true }
console.log(item.next());   // { value: undefined, done: true }

小结:

generator对象只会在调用next时开始执行;
函数在执行到yield语句时会暂停并返回yield的值
函数在next被调用时继续恢复执行
generator函数的执行与否是由外部的generator对象控制的

出来yield传值到外部,也可以通过next传值到内部进行调用

function* generator() {
    // 接受next传入参数并赋值
    let item1 = yield;
    console.log(item1);
    // 返回值
    yield item1 + 1;
}

let iterator = generator();
iterator.next();  // {value: undefined, done: false}
console.log(iterator.next('123'));
/*
123
{value: '1231', done: false }
*/

throw函数处理迭代器内部报错

function* generator() {
    try{
        yield 1;
    }catch (e) {
        console.log(e.message);
    }
}
let iterator = generator();
console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.throw(new Error('error')));
/*
error
{value: undefined, done: true}
*/

小结:外部可对generator内部进行干涉
外部可传递值到generator函数体中
外部可抛入一个异常到generator函数体中

generator函数的使用场景

1. 异步操作的同步化表达

将异步操作写在yield表达式里面,用来处理异步操作,改写回调函数。
function* loadUI() {
    showLoadingScreen();
    yield loadUIDataAsynchronously();   // 异步加载UI数据
    hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next();
// 卸载UI
loader.next();

// 用同步的方式表达Ajax(异步)
function* main() {
    var result = yield request("http://some.url");
    var resp = JSON.parse(result);
    console.log(resp.value);
}
function request(url) {
    makeAjaxCall(url, function(response){
        it.next(response);
    });
}
var it = main();
it.next();

2. 控制流管理

如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样
step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                
            }
        }
    }
})

采用Promise改写上面的代码

Promise.resolve(step1)
    .then(step2)
    .then(step3)
    .then(step4)
    .then(function (value4) {
        
    }, function (error) {
        
    })
    .done();

上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量Promise的语法。Generator函数可以进一步改善代码运行流程。

function* longRunningTask(value1) {
    try {
        var value2 = yield step1(value1);
        var value3 = yield step1(value2);
        var value4 = yield step1(value3);
        var value5 = yield step1(value4);
    } catch (e) {
        
    }
}

然后,使用一个函数,按次序自动执行所有步骤。

scheduler(longRunningTask(initialValue));
function scheduler(task) {
    var taskObj = task.next(task.value);
    // 如果Generator函数未结束,就继续调用
    if (!taskObj.done) {
        task.value = taskObj.value;
        scheduler(task);
    }
}

以上方法只适合同步操作,即所有的task都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。

使用for...of循环会自动执行yield命令的特性,提供一种更一般的控制流管理的方法
let steps = [step1Func, step2Func, step3Func];

function* interateSteps(steps){
for (var i = 0; i < steps.length; i++) {
var step = step [i];
yield step();
}
}

3. 部署Iterator接口

利用Genertator函数,可以在任意对象上部署Iterator接口。
function* iterEntries(obj) {
    let keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        yield [key, obj[key]];
    }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
    console.log(key, value);
}

// foo 3
// bar 7

对数组部署Iterator 接口的例子,尽管数组原生具有这个接口。

function* makeSimpleGenerator(array) {
    var nextIndex = 0;
    while(nextIndex < array.length) {
        yield array[nextIndex++];
    }
}
var gen = makeSimpleGenerator(['yo', 'ya']);

gen.next().value;  // 'yo'
gen.next().value;  // 'ya'
gen.next().done;  // true

4. 作为数据结构

Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。
function* doStuff() {
    yield fs.readFile.bind(null, 'hello.txt');
    yield fs.readFile.bind(null, 'world.txt');
    yield fs.readFile.bind(null, 'and-such.txt');
}

上面代码就是依次返回三个函数,但是由于使用了Generator函数,导致可以像处理数组那样,处理这三个返回的函数。

for (task of doStuff()) {
    // task是一个函数,可以像回调函数那样使用它
}

实际上,如果用ES5表达,完全可以用数组模拟Generator的这种用法。

function doStuff() {
    return [
        fs.readFile.bind(null, 'hello.txt');
        fs.readFile.bind(null, 'world.txt');
        fs.readFile.bind(null, 'and-such.txt');
    ]
}

Class

Class, 有constructor、extendssuper, 但本质上语法糖(对语言的功能并没有影响,但是更方便开发人员使用)。
Class Artist {
    constructor(name) {
        this.name = name;
    }
    
    perform() {
        return this.name + "performs";
    }
}

Class Singer extends Artist {
    constructor(name, song) {
        super.constructor(name); // 继承父类的构造函数
        this.song = song
    }
    perform() {
        return super.perform() + "[" + this.song + "]";
    }
}

let james = new Singer("Etta James", "At last");
james instanceof Artist;  // true
james instanceof Singer;  // true

james.perform(); // "Etta James perform [At l ast]"

Modules

ES6的内置模块功能借鉴了CommonJS和AMD各自的优点:
  • 具有CommonJS的精简语法、唯一导出出口(single exports)和循环依赖(cyclic dependencies)的特点。
  • 类似AMD,支持异步加载和可配置的模块加载。
// lib/math.js
export function sum (x, y) {
    return x + y;
}
export var pi = 3.141593;

// app.js
import * as math form "lib/math";
alert("2Π = " + math.sum(math.pi, math.pi));

// otherApp.js
import {sum, pi} form "lib/math";
alert("2Π = " + sum(pi, pi));

Module Loaders:
// Dynamic loading - `System` is default loader
System.import('lib/math').then(function(m) {
    alert("2Π = " + m.sum(m.pi, m.pi));
});

// Directly manipulate module cache
System.get('jquery');
System.set('jquery', Module({$: $}));   // WARNING: not yet finalized

Map + Set + WeakMap + WeakSet

四种集合类型,WeakMapWeakSet作为属性键的对象如果没有别的变量在引用它们,则会被回收释放掉。
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");  // Set具有去重的功能,即是hello被添加了两次实际上也只会添加进一次
s.size === 2;   // true
s.has("hello") === true;   // true

// Maps
var m = new Map();
m.set("hello", 42);  // {"hello" => 42}
m.set(s, 34);  // {"hello" => 42, Set(2) => 34}
m.get(s) == 34;  // true

// WeakMap
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined;  // true

// WeakSet
var ws = new WeakSet();
ws.add({ data: 42 });   // 添加的{ data: 42 }没有其他的引用,因此实际上并没有被添加,ws还是空的

Math + Number + String + Array + Object APIs

// Number新增的API
Number.EPSILON  //表示1与大于1的最小浮点数之间的差
Number.isInterger(Infinity);   // false  判断Infinity是否是整数
Number.isNaN("NaN");  // false  判断字符串NaN是否是非数字,因为"NaN"会进行隐式类型转换

// Math新增的API
Math.acosh(3);   // 1.762747174039086   返回3的双曲反余弦值
Math.hypot(3, 4);  // 5     返回3,4平方和的平方根
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2);  // 2  返回两个参数的类 C 32 位乘法的结果

// String新增的API
"adcde".includes("cd");  // true    判断字符串中是否包含cd
"abc".repeat(3);   // "abcabcabc"   将字符串重复3次组成新的字符串

// Array新增的API
Array.from(document.querySelectorAll('*')) // Returns a real Array    判断参数是否是真正的数组
Array.of(1, 2, 3) // 创建数组,和new Array(...)类似,只是Array.of ()方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

//例如:
let arr = Array.of(4);
console.log(arr); // [4]
console.log(arr.length); // 1
let arr1 = new Array(4);
console.log(arr.length); // 4

[0, 0, 0].fill(7, 1) // [0,7,7]    填充数组,总共有三个参数,第一个参数表示填充值,第二个参数表示填充起始位置,可以省略。第三个参数表示填充结束位置,可以省略,实际结束位置是end-1。
[1, 2, 3].find(x => x == 3) // 3  返回满足条件的第一个值
[1, 2, 3].findIndex(x => x == 2) // 1  返回满足条件的第一个值的索引
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]  用于从数组的指定位置拷贝元素到数组的另一个指定位置中
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

// Object新增的API
Object.assign(Point, { origin: new Point(0,0) });  // 将两个或多个对象合并成一个对象

Proxies

使用代理(Proxy)监听对象的操作,然后可以做一些相应事情。
var target = {};
var handler = {
    get: function (receiver, name) {
        return `Hello, ${name}!`;
    }
};
var p = new Proxy(target, handler);
p.world === 'hello, world!';   // true

可以监听的操作: get、set、has、deleteProperty、apply、construct、getOwnPropertyDescriptor、defineProperty、getPrototypeOf、setPrototypeOf、enumerate、ownKeys、preventExtensions、isExtensible

Symbols

Symbol是一种基本类型。Symbol通过调用Symbol函数产生,它接收一个可选的名字参数,该函数返回的Symbol是唯一的。

Promises

Promises是处理异步操作的对象,使用了Promise对象之后可以用一种链式调用的方式来组织代码,让代码更加直观(类似jQuery的deferred对象)
    return new Promise(function (resolve, reject) {
        // setTimeouts are for effect, typically we would handle XHR
        if (!url) {
            return setTimeout(reject, 1000);
        }
        return setTimeout(resolve, 1000);
    });
}

// no url, promise rejected
fakeAjax().then(function() {
    console.log('success');
}, function () {
    console.log('fail');
});

针对以上知识,可以看下面内容,系统学习
JavaScript标准参考教程(alpha)
ECMAScript6入门