ES6

E6的不同

  • 新增模板字符串(为JavaScript提供了简单的字符串插值功能)
  • 箭头函数
  • for-of(用来遍历数据—例如数组中的值。)
  • arguments对象可被不定参数和默认参数完美代替。
  • ES6将promise对象纳入规范,提供了原生的Promise对象。
  • 增加了let和const命令,用来声明变量。
  • 增加了块级作用域。
  • et命令实际上就增加了块级作用域。
  • 还有就是引入module模块的概念

47 ECMAScript6 怎么写

var、let 及 const 区别?

  • let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。

  • let 和 const 是JS中的块级作用域。

  • let 和 const 不允许重复声明(会抛出错误)。

  • let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。

  • const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)。

解构赋值?

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

  1. 数组的解构赋值

数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应 如果有对应不上的就是 undefined。

var [name, pwd, sex] = ["Oli", "123456", "男"];
console.log(name)  //-->Oli
console.log(pwd)  //-->123456
console.log(sex)  //-->男

  1. 对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

var obj = {name: "Oli", pwd: "123456", sex: "男"}
var {name, pwd, sex} = obj;
console.log(name)  //-->Oli
console.log(pwd)  //-->123456
console.log(sex)  //-->男

JavaScript 中什么是变量提升?什么是暂时性死区?

变量提升就是变量在声明之前就可以使用,值为 undefined。

在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。暂时性死区也意味着 typeof 不再是一个百分百安全的操作。

typeof x;  //ReferenceError(暂时性死区,报错)
let x;

typeof y;  //值 undefined,不会报错。

暂时性死区的本质就是:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取。只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

箭头函数与普通函数有什么区别?

  • 箭头函数是匿名函数,不能作为构造函数,不能使用 new。
var B = () => {
  value:1;
}
var b = new B();  //-->TypeError: B is not a constructo
  • 箭头函数不绑定 arguments,取而代之用 rest 参数 … 解决
function A(a) {
  console.log(arguments); 
}

var B = (b) => {
  console.log(arguments); 
}

//...c 即为 rest 参数
var C = (...c) => { 
  console.log(c); 
}
A(1);  //-->[object Arguments] {0: 1}
B(2);  //-->ReferenceError: arguments is not defined
C(3);  //-->[3]
  • 箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定。
  • 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
var obj = {
  a: 10,
  b: function() {
    console.log(this.a); 
  },
  c: function() {
     return () => {
       console.log(this.a);
     }
  }
}
obj.b();  //-->10
obj.c()();  //-->10
  • 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
var obj = {
  a: 10,
  b: function(n) {
    var f = (v) => v + this.a;
    return f(n);
  },
  c: function(n) {
    var f = (v) => v + this.a;
    var m = {a:20};
    return f.call(m,n);
  }
}

console.log(obj.b(1));  //-->11
console.log(obj.c(1));  //-->11

  • 箭头函数没有原型属性。
var a = () => {
  return 1;
}
function b() {
  return 2;
}
console.log(a.prototype);  //-->undefined
console.log(b.prototype);  //-->object{...}

使用 ES6 改下面的模板?

let iam  = "我是";
let name = "Oli";
let str  = "大家好,"+iam+name+",多指教。";

答:

let iam  = `我是`;
let name = `Oli`;
let str  = `大家好,${iam+name},多指教。`;

call apply bind 区别 及 实现

  • bind:fun.bind(thisArg,arg1,arg2,...)

它是直接改变这个函数的 this 指向并且返回一个新的函数,之后再次调用这个函数的时候 this 都是指向 bind 绑定的第一个参数。bind 传参方式跟 call 方法一致。

bind的作用与call和apply相同,区别是call和apply是立即调用函数,而bind是返回了一个函数,需要调用的时候再执行。

thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。

arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

  • call:fun.call(thisArg, arg1, arg2, …)

call 跟 apply 的用法几乎一样,唯一的不同就是传递的参数不同,call 只能一个参数一个参数的传入。

thisArg: 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

arg1, arg2, … 指定的参数列表。

  • apply

apply 则只支持传入一个数组,哪怕是一个参数也要是数组形式。最终调用函数时候这个数组会拆成一个个参数分别传入。

thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。

.总结:

  • 当我们使用一个函数需要改变 this 指向的时候才会用到 call、apply、bind;

  • 如果你要传递的参数不多,则可以使用 fn.call(thisObj, arg1, arg2 …);

  • 如果你要传递的参数很多,则可以用数组将参数整理好调用 fn.apply(thisObj, [arg1, arg2 …]);

  • 如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用 const newFn = fn.bind(thisObj); newFn(arg1, arg2…);

  • call 和 apply 第一个参数为 null/undefined,函数 this 指向全局对象,在浏览器中是 window,在 node 中是 global。

  • 调用时:

xw.say.call(xh,m,n);
xw.say.apply(xh,[m,n]);
xw.say.bind(xh,m.n)();||  xw.say.bind(xh)(m,n);

实现

call,apply,bind 都是改变函数执行的上下文,说的直白点就是改变了函数 this 的指向。

不同的是:call 和 apply 改变了函数的 this ,并且执行了该函数,而 bind 是改变了函数的 this,并返回一个函数,但不执行该函数。

看下面的例子:

var doThu = function(a, b) {
  console.log(this)
  console.log(this.name)
  console.log([a, b])
}
var stu = {
  name: "Oli",
  doThu: doThu,
}
stu.doThu(1, 2)  
//-->{name: "Oli", doThu: ƒ}
//-->Oli
//-->[1, 2]

doThu.call(stu, 1, 2)
//-->{name: "Oli", doThu: ƒ}
//-->Oli
//-->[1, 2]

由此可见,在 stu 上添加一个属性 doThu,再执行这个函数,就将 doThu 的 this 指向了 stu。而 call 的作用就与此相当,只不过 call 为 stu 添加了 doThu 方法后,执行了 doThu,然后再将 doThu 这个方法从 stu 中删除。

call 函数的内部实现原理:

Function.prototype.call = function(thisArg, args) {
  //this 指向调用 call 的对象
  if(typeof this !== "function") { 

    //调用 call 的若不是函数则报错
    throw new TypeError("Error")
  }
  thisArg = thisArg || window

  //将调用 call 函数的对象添加到 thisArg 的属性中。
  thisArg.fn = this   

  //执行该属性
  const result = thisArg.fn(...[...arguments].slice(1)) 

  //删除该属性
  delete thisArg.fn   
  return result
}


apply 的实现原理和 call一样,只不过是传入的参数不同而已:

Function.prototype.apply = function(thisArg, args) {
  if(typeof this !== "function") { 
    throw new TypeError("Error")
  }
  thisArg = thisArg || window
  thisArg.fn = this
  let result
  if(args) {
    result = thisArg.fn(...args)
  }else {
    result = thisArg.fn()
  }
  delete thisArg.fn
  return result
}

bind实现

bind 的实现原理比 call 和 apply 要复杂一些,bind 中需要考虑一些复杂的边界条件。bind 后的函数会返回一个函数,而这个函数也可能被用来实例化:

Function.prototype.bind = function(thisArg) {
  if(typeof this !== "function") {
    throw new TypeError(this + "must be a function");
  }
  //存储函数本身
  const _this  = this;

  //去除 thisArg 的其他参数 转成数组
  const args = [...arguments].slice(1)

  //返回一个函数
  const bound = function() {

    //可能返回了一个构造函数,我们可以 new F(),所以需要判断。
    if(this instanceof bound) {
    return new _this(...args, ...arguments)
    }

    //apply 修改 this 指向,把两个函数的参数合并传给 thisArg 函数,并执行 thisArg 函数,返回执行结果。
    return _this.apply(thisArg, args.concat(...arguments))
  }
  return bound
}

import 和 export?

ES6 标准中,JavaScript 原生支持模块(module)了。这种将 JS 代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。

  • export 用于对外输出本模块(一个文件可以理解为一个模块)变量的接口。

  • import 用于在一个模块中加载另一个含有 export 接口的模块。

  • import 和 export 命令只能在模块的顶部,不能在代码块之中。

//导入部分:
import {name} from "./example"
//全部导入:
import Person from "./example"

//将整个模块所有导出内容当做单一对象,用 as 起别名。
import * as example from "./example.js"
console.log(example.name)
console.log(example.getName())

//导出部分
export {firstName, lastName, year};
//导出默认:
export default App

ES6 中的 class 了解吗?ES6 中的 class 和 ES5 的类有什么区别?

  • ES6 class 内部所有定义的方法都是不可枚举的;

  • ES6 class 必须使用 new 调用;

  • ES6 class 不存在变量提升;

  • ES6 class 默认即是严格模式;

变量必须声明后再使用;

函数的参数不能有同名属性,否则报错;

不能使用 with 语句;

禁止 this 指向全局对象。

  • ES6 class 子类必须在父类的构造函数中调用 super(),这样才有 this 对象;ES5 中类继承的关系是相反的,先有子类的 this,然后用父类的方法应用在 this 上。

Promise 对象

简介:

  • Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
  • Promise对象是一个构造函数,用来生成Promise实例。
  • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法返回的是一个新的Promise实例,因此可以采用链式写法.
  • Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数,也是返回一个新的Promise对象。
  • Promise.prototype.finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
  • Promise.all方法用于将多个 Promise(p1,p2,p3) 实例,包装成一个新的 Promise 实例。
只有p1~3全部状态变为fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

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

如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

  • Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
  • Promise.resolve()将现有对象转为 Promise 对象,调用时若不带参数,直接返回一个resolved状态的 Promise 对象。
  • Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

Promise 有几种状态?Promise 的特点是什么,分别有什么优缺点?

  • Promise 有三种状态: fulfilled,rejected,pending。

  • 特点:
    对象的状态不受外界影响
    一旦状态改变,就不会再变,任何时候都可以得到这个结果。

  • Promise 的优点:

一旦状态改变,就不会再变,任何时候都可以得到这个结果;

可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数形成的回调函数地狱。

  • Promise 的缺点:

无法取消 Promise

错误需要通过回调函数来捕获;如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

当处于 pending 状态时,无法得知目前进展到哪一个阶段。

promise的链式调用

  • 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)
  • 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调
  • 如果then中出现异常,会走下一个then的失败回调
  • 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)
  • then中可以不传递参数,如果不传递会透到下一个then中(见例3)
  • catch 会捕获到没有捕获的异常

Iteraror and for…of

定义

  • Iteraror是一种接口机制,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署了Iterator解构,就可以完成便利操作。
  • 一种数据结构只要部署了iterator解构,就称这种数据结构是可遍历的

具备iterator接口的数据结构

  • Array
  • String
  • arguments
  • NodeList
  • Map
  • Set
  • TypeArray

作用

  • 为各种数据结构提供统一的访问接口
  • 使数据结构内部成员按某种次序排列
  • 与ES6提供的for…of遍历命令对接

原理分析

  • 接口部署在数据结构的Symbol.iterator属性
  • 遍历器对象本质是指针对象
  • 调用next()移动指针执行
  • next()方法返回包含value与done属性的对象(value是返回值,done代表遍历是否结束,(Generator函数是否执行完毕))

show code

const obj = {
  [Symbol.iterator] : function () {//是symbol对象所以要放在方括号中 不然会被识别为字符串
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

Generator

定义

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

Generator 最大的特点就是可以控制函数的执行。

  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
  • Generator 函数除了状态机,还是一个遍历器对象生成函数。
  • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
  • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值(第一次调用无需传参,此后不传会默认传入undefined)。

特征

  • function关键字与函数名之间有一个*号
  • 内部使用yield表达式定义不同状态

code

function* foo(x) {
  let y = 2 * (yield (x + 1));
  let z = yield (y + 'Y');
  return (x + '-' + y + '-' + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 24Y, done: false}
console.log(it.next()) // => {value:5-24-undefined , done: true}

CO模块

  • 手动迭代Generator 函数很麻烦,实现逻辑有点绕,而实际开发一般会配合 co 库去使用。
  • co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,用于 Generator 函数的自动执行
  • co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。

example

  • 有三个本地文件,分别1.txt,2.txt和3.txt,内容都只有一句话,下一个请求依赖上一个请求的结果,想通过Generator函数依次调用三个文件

文件

//1.txt文件
2.txt

//2.txt文件
3.txt

//3.txt文件
结束

引入co

npm install co

代码实现

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}

function* r() {
  let r1 = yield read('./1.txt')
  let r2 = yield read(r1)
  let r3 = yield read(r2)
  console.log(r1)
  console.log(r2)
  console.log(r3)
}

let co = require('co')
co(r()).then(function(data) {
  console.log(data)
})
// 2.txt=>3.txt=>结束=>undefined

Async/Await

定义

  • 是 Generator 函数的语法糖,async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
  • async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
  • async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

用法

  • async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async函数对 Generator 函数的改进

体现在以下四点。

  • 内置执行器
    Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。
  • 更好的语义
    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • 更广的适用性
    co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  • 返回值是 Promise。
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

async函数与Promise

  • async函数内部return语句返回的值,会成为then方法回调函数的参数。
  • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到所以最好把await命令放在try…catch代码块中。。
  • async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

async函数相对于Promise的优势

  • 处理 then 的调用链,能够更清晰准确的写出代码
  • 并且也能优雅地解决回调地狱问题。

async函数的缺点

  • 缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。
  • 解决办法:Promise.all

async注意点以及 并发请求

  • await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
  • await命令只能用在async函数之中,如果用在普通函数,就会报错。
  • async 函数可以保留运行堆栈。
  • 多个await命令后面的异步操作,如果不存在继发关系(户不依赖),最好让它们同时触发。

例1

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。

例2
如果请求两个文件,毫无关系,可以通过并发请求

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function readAll() {
  read1()
  read2()//这个函数同步执行
}
async function read1() {
  let r = await read('1.txt','utf8')
  console.log(r)
}
async function read2() {
  let r = await read('2.txt','utf8')
  console.log(r)
}
readAll() // 2.txt 3.txt

Proxy

Proxy:阮一峰

  • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,用来自定义对象中的操作
  • ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
  • 注意,要使得Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作。
  • 如果handler没有设置任何拦截,那就等同于直接通向原对象。

常用拦截操作

get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']
set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
defineProperty(target, propKey, propDesc):拦截

使用 Proxy 来实现一个数据绑定和监听

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      setBind(value);
      return Reflect.set(target, property, value);
    }
  };
  return new Proxy(obj, handler);
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值