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 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
- 数组的解构赋值
数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应 如果有对应不上的就是 undefined。
var [name, pwd, sex] = ["Oli", "123456", "男"];
console.log(name) //-->Oli
console.log(pwd) //-->123456
console.log(sex) //-->男
- 对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
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);
};