【学习笔记】ECMAScript 6

Posted by wml on October 17, 2018

1、变量分为原始类型和引用类型;

原始类型:存储在栈中(stack)的简单数据段,值直接存储在变量的访问位置;

引用类型: 存储在堆中(heap)的对象,存储在变量处的值是一个指针,指向存储对象的内存处。

原始类型:

  • Undefined,Null, Boolean, Number, String.这些原始类型占据的空间固定,所以可以将他们存储在较小的内存区域-栈中,便于迅速查询变量的值。
  • typeof null 返回Object,js最开始实现的错误,后被ECMAscript, 认为null是对象占位符。
  • null == undefiend; //输出true,因为undefined实际上是null派生来的。
  • 特殊的Number值:
    a)、Number.MAX_VALUE和Number.MIN_VALUE,数在此区间,当计算结果超出这个范围,被赋予Number.POSITIVE_INFINITY和Number.NEGATIVE_INFINITY,结果不能再用于计算; 有专门的值表示无穷大,infinity;Number.POSITIVE_INFINITY为infinity,Number.NEGATIVE_INFINITY为-infinity;
    b)、NAN,也不能用于算术计算,且NAN == NAN // 输出false

2、this

函数是也是一个对象,this总是指向调用该方法的对象(永远指向当前正在被执行的函数和方法的owner)。引用对象属性时,必须使用this关键字,eg

function showColor() {
  alert(
this.color
);  //不用this报错,因为没有color的局部或全局变量
};

var oCar1 = new Object;
oCar1.color = "red";
oCar1.showColor = showColor;

oCar1.showColor(); //输出 "red"

3、apply和call的作用:

简单来说是,当一个对象实例缺少一个函数/方法时,可以调用其他对象的现成函数/方法,其方式是通过替换其中的this为这个对象实例,改变函数运行时的上下文。eg:

function Dog(){
    this.sound="汪汪汪";
}
Dog.prototype.bark=function(){
    alert(this.sound);
}
var cat = {sound: '喵喵喵 '};
Dog.prototype.bark.call(cat); // 或 Dog.bark.call(cat);

4、 普通函数和构造函数

普通函数:

  • 不需要使用new关键字调用;
  • 可以用return语句返回值;
  • 函数内部不建议使用this关键字;
  • 函数命名以驼峰方式,首字母小写。

构造函数:

js中,用new关键字来调用定义的构造函数,默认返回的是一个新对象,这个新对象具有构造函数定义的变量和函数/方法。eg:

function Prince(name, age) {
    this.gender = 'male';
    this.kind = true;
    this.rich = true;
    this.name = name;
    this.age = age;
}
Prince.prototype.toFrog = function() {
    console.log("Prince"+this.name+"turned into a frog.");
}
var prince = new Prince('charming', 25);
prince.toFrog(); //Prince charming turned into a frog.
prince.kind;//true

构造函数特点:

  • 用new关键字调用;
  • 函数内部可以使用this关键字,在构造函数内部,this指向的是构造出的新对象。用this定义的变量或函数/方法,就是实例变量或实例函数/方法。需要用实例才能访问到,不能用类型名访问。
prince.age;//25
Prince.age;//undefined
  • 默认不用return返回值
  • 函数命名建议首字母大写,与普通函数区分开。

5.new关键字实例化时发生什么

  1. 创建一个空对象;var prince={}
  2. 将构造函数Prince()中的this指向新创建的对象prince。
  3. 将prince的_proto_属性指向Prince函数的prototype,创建对象和原型间关系
  4. 执行构造函数Prince()内的代码。

6、同源策略

  • 协议相同;
  • 域名相同;
  • 端口相同; 默认80

非同源则:

  • Cookie、LocalStorage 和 IndexDB 无法读取;
  • DOM 无法获得;
  • AJAX 请求不能发送。

7、es6

1)、let: 声明的变量只在代码块内有效;不存在变量提升,所以要先声明后使用。

暂时性死区(temporal dead zone,简称TDZ):只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

typeof不再是一个百分之百安全的操作, typeof x; // ReferenceError

let不允许在相同作用域内,重复声明同一个变量。

2)、变量的解构赋值:

let [x, y] = [1, 2, 3];

symbol

ES5的对象属性名都是字符串,这容易造成属性名的冲突。ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型;凡是属性名属于Symbol类型,就都是独一无二的。

Symbol函数的参数只是表示对当前Symbol值的描述。

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");

s1 === s2 // false
var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Generator

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

yield语句不能用在普通函数中,否则会报错。

yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

Promise

Promise是异步编程的一种解决方案,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise是一个对象,从它可以获取异步操作的消息。

Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

var promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise实例生成后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。then方法可以接受两个回调函数作为参数,第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数(可选的)是Promise对象的状态变为Reject时调用,这两个函数都接受Promise对象传出的值作为参数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise新建后就会立即执行。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
promise.then(function() {
  console.log('Resolved.');
});
console.log('Hi!');

// Promise
// Hi!
// Resolved

Promise.prototype.then()作用是为Promise实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

Promise.prototype.catch(),方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected。此时第一个被reject的实例的返回值,会传递给p的回调函数。

var p = Promise.all([p1, p2, p3]);

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。

var p = Promise.race([p1,p2,p3]);

Promise.resolve方法,立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(’one‘)则是立即执行,因此最先输出。

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。

async函数

ES7提供了async函数,使得异步操作变得更加方便。async函数就是Generator函数的语法糖。

var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async函数是对 Generator 函数的改进,(1)内置执行器,(2)更好的语义,(3)更广的适用性,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。(4)返回值是Promise。

es6模块化

Node的默认模块格式是CommonJS,目前还没决定怎么支持ES6模块。所以,只能通过Babel这样的转码器,在Node里面使用ES6模块。

// ES6模块
import { stat, exists, readFile } from 'fs';

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

export

export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1;

// 报错
var m = 1;
export m;

// 报错
function f() {}
export f;
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

// 正确
export function f() {};

// 正确
function f() {}
export {f};
import

import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

import {firstName, lastName, year} from './profile';

es7提案

// 提案的写法
export v from 'mod';

// 现行的写法
export {v} from 'mod';
整体加载
import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default

使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载,为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';  //import命令后面,不使用大括号。
customName(); // 'foo'

下面代码,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。

// 输出
export default function crc32() {
  // ...
}
// 输入
import crc32 from 'crc32';

// 输出
export function crc32() {
  // ...
};
// 输入
import {crc32} from 'crc32';

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export deault命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能对应一个方法。

正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

如果想在一条import语句中,同时输入默认方法和其他变量,可以写成下面这样。

import customName, { otherMethod } from './export-default';

编程风格

在全局环境,不应该设置变量,只应设置常量。这符合函数式编程思想,有利于将来的分布式运算。

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

// good
const a = 'foobar';
const b = `foo${a}bar`;

使用数组成员对变量赋值时,优先使用解构赋值。

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

使用扩展运算符(…)拷贝数组。

// good
const itemsCopy = [...items];

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。