ES6主要相关问题重学
1、let、const 声明
let声明
1、ES6新增了let来声明变量,用法类似于var,但声明的变量,只在let命令所在的代码块内有效。也不同于var声明的变量“提升”现象(变量在声明前使用,值为undefined)。let所声明的变量一定要在声明后使用,不然会报错。
{
let a = 10;
var b = 1;
}
alert(b); // 1
alert(a); // a is not defind
console.log(foo); // 输出undefined
var foo = 2;
console.log(bar); // 谷歌下undefined
let bar = 2;
2、块级作用域内存在let所声明的变量会绑定在这个块级作用域内,外部影响不了。在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var tmp = 123;
if (true) {
tmp = 'abc';
let tmp;
alert(tmp); //undefind
let str = 'ABC';
alert(str); //ABC
}
3、let不允许在相同作用域内,重复声明同一个变量。
function test(){
var a = 10;
var a = 1;
}
function test(){
let a = 10;
var a = 1; //Duplicate declaration "a"
}
function test(){
let a = 10;
let a = 1; //Duplicate declaration "a"
}
4、let实际上为 JavaScript 新增了块级作用域,内层作用域可以定义外层作用域的同名变量,外层作用域无法读取内存作用域的变量
(function() {
var n = 5;
if (true) {
var n = 10;
var m =100;
}
alert(n); // 10
alert(m); // 100
})();
(function() {
let n = 5;
if (true) {
let n = 10; //内层作用域可以定义外层作用域的同名变量
let m =100;
}
alert(n); // 5
alert(m); // m is not defined
})();
由上面可以看出var和let的不同,let外层代码块不受内层代码块的影响。内层作用域可以定义外层作用域的同名变量,外层作用域无法读取内存作用域的变量,所以m才会not defined。ES6还允许块级作用域的任意嵌套,例如:
{{{{{let insane = 'Hello World'}}}}};
5、ES6明确允许在块级作用域之中声明函数,ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
(function(){
if(true){
function func(){
alert("我被执行了");
}
func(); // 我被执行了
}
func(); // ES6环境下func is not defined 但是ES5环境下弹出"我被执行了"
})();
上面代码在 ES5 中运行,会得到“我被执行了”,因为在if内声明的函数func会被提升到函数头部,但是在ES6环境中,块级作用域内声明的函数类似于let,对作用域之外没有影响。
const声明
const声明一个只读的常量。一旦声明,要立即初始化,不能留到以后赋值。初始化后的常量的值不能改变。
const name = 'perter';
console.log(name); //perter
name = 'Tom'; //"name" is read-only
只声明不赋值:const name ; //Unexpected token
const和let相同之处:
只在声明所在的块级作用域内有效;
声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用;
声明的常量一样不可重复声明
const的本质不是保证变量的值不变,而是要保证指向的那个内存地址是不得改动的,这就是常量。对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,这个指针式固定不可变的,但数据结构可以
如我用一个常量声明一个对象,常量保存的是指向这个对象的指针,对象属性的重写或添加是被允许的,但不能再把这个常量指向另一个对象:
const obj = {
name: '张三',
age:20
}
console.log(obj.name); //张三
obj.name = '李四';
console.log(obj.name); //李四
obj = { name: '张三'}; //"obj" is read-only
如果要让属性也不能再重写或添加可以用Object.freeze方法,将常量obj指向一个冻结的对象:
const obj = Object.freeze({});
obj.name = '张三'; //Cannot add property name, object is not extensible
2、解构赋值
概念
解构赋值主要分为对象的解构和数组的解构,在没有解构赋值的时候,我们赋值是这样的
let arr = [0,1,2]
let a = arr[0]
let b = arr[1]
let c = arr[2]
这样写很繁琐,那么我们有没办法既声明,又赋值,更优雅的写法呢?肯定是有的,那就是解构赋值,解构赋值,简单理解就是等号的左边和右边相等。
一、数组的解构赋值
let arr = [0,1,2]
let [a,b,c] = arr
console.log(a) // 0
console.log(b) // 1
console.log(c) // 2
但是很多时候,数据并非一一对应的,并且我们希望得到一个默认值
let arr = [,1,2]
let [a='我是默认值',b,c] = arr
console.log(a) // '我是默认值'
console.log(b) // 1
console.log(c) // 2
从这个例子可以看出,在解构赋值的过程中,a=undefined时,会使用默认值 那么当a=null时呢?当a=null时,那么a就不会使用默认值,而是使用null
// 数组的拼接
let a = [0,1,2]
let b = [3,4,5]
let c = a.concat(b)
console.log(c) // [0,1,2,3,4,5]
let d = [...a,...b]
console.log(d) // [0,1,2,3,4,5]
// 数组的克隆
// 假如我们简单地把一个数组赋值给另外一个变量
let a = [0,1,2,3]
let b = a
b.push(4)
console.log(a) // [0,1,2,3,4]
console.log(b) // [0,1,2,3,4]
因为这只是简单的把引用地址赋值给b,而不是重新开辟一个内存地址,所以
a和b共享了同一个内存地址,该内存地址的更改,会影响到所有引用该地址的变量
那么用下面的方法,把数组进行克隆一份,互不影响
let a = [0,1,2,3]
let b = [...a]
b.push(4)
console.log(a) // [0,1,2,3]
console.log(b) // [0,1,2,3,4]
二、对象的解构赋值
对象的解构赋值和数组的解构赋值其实类似,但是数组的数组成员是有序的
而对象的属性则是无序的,所以对象的解构赋值简单理解是等号的左边和右边的结构相同
let {name,age} = {name:"swr",age:28}
console.log(name) // 'swr'
console.log(age) // 28
对象的解构赋值是根据key值进行匹配
// 这里可以看出,左侧的name和右侧的name,是互相匹配的key值
// 而左侧的name匹配完成后,再赋值给真正需要赋值的Name
let { name:Name,age } = { name:'swr',age:28 }
console.log(Name) // 'swr'
console.log(age) // 28
那么当变量已经被声明了呢?
let name,age
// 需要用圆括号,包裹起来
({name,age} = {name:"swr",age:28})
console.log(name) // 'swr'
console.log(age) // 28
变量能否也设置默认值?
let {name="swr",age} = {age:28}
console.log(name) // 'swr'
console.log(age) // 28
// 这里规则和数组的解构赋值一样,当name = undefined时,则会使用默认值
let [a] = [{name:"swr",age:28}]
console.log(a) // {name:"swr",age:28}
let { length } = "hello swr"
console.log(length) // 9
function ajax({method,url,type='params'}){
console.log(method) // 'get'
console.log(url) // '/'
console.log(type) // 'params'
}
ajax({method:"get",url:"/"})
三、扩展运算符
我们先看下代码,在以往,我们给函数传不确定参数数量时,是通过arguments来获取的
function sum() {
console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
// 我们可以看出,arguments不是一个数组,而是一个伪数组
let total = 0
let { length } = arguments
for(let i = 0;i < length;i++){
total += arguments[i]
}
return total
}
console.log(sum(1,2,3,4,5,6)) // 21
接下来我们用扩展运算符看看
function sum(...args){ // 使用...扩展运算符
console.log(args) // [ 1, 2, 3, 4, 5, 6 ] args是一个数组
return eval(args.join('+'))
}
console.log(sum(1,2,3,4,5,6)) // 21
得到的args是一个数组,直接对数组进行操作会比对伪数组进行操作更加方便,还有一些注意点需要注意
// 正确的写法 扩展运算符只能放在最后一个参数
function sum(a,b,...args){
console.log(a) // 1
console.log(b) // 2
console.log(args) // [ 3, 4, 5, 6 ]
}
sum(1,2,3,4,5,6)
// 错误的写法 扩展运算符只能放在最后一个参数
function sum(...args,a,b){
// 报错
}
sum(1,2,3,4,5,6)
我们可以对比下扩展运算符的方便之处
// 以往我们是这样拼接数组的
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = arr1.concat(arr2)
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
// 现在我们用扩展运算符看看
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [...arr1,...arr2]
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
// 以往我们这样来取数组中最大的值
function max(...args){
return Math.max.apply(null,args)
}
console.log(max(1,2,3,4,5,6)) // 6
// 现在我们用扩展运算符看看
function max(...args){
return Math.max(...args) // 把args [1,2,3,4,5,6]展开为1,2,3,4,5,6
}
console.log(max(1,2,3,4,5,6)) // 6
// 扩展运算符可以把argument转为数组
function max(){
console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
let arr = [...arguments]
console.log(arr) // [1,2,3,4,5,6]
}
max(1,2,3,4,5,6)
// 但是扩展运算符不能把伪数组转为数组(除了有迭代器iterator的伪数组,如arguments)
let likeArr = { "0":1,"1":2,"length":2 }
let arr = [...likeArr] // 报错 TypeError: likeArr is not iterable
// 但是可以用Array.from把伪数组转为数组
let likeArr = { "0":1,"1":2,"length":2 }
let arr = Array.from(likeArr)
console.log(arr) // [1,2]
对象也可以使用扩展运算符
// 以往我们这样合并对象
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {}
Object.assign(person,name,age)
console.log(person) // { name: '邵威儒', age: 28 }
// 使用扩展运算符
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {...name,...age}
console.log(person) // { name: '邵威儒', age: 28 }
需要注意的是,通过扩展运算符和Object.assign对对象进行合并的行为,是属于浅拷贝,那么我们在开发当中,经常需要对对象进行深拷贝,接下来我们看看如何进行深拷贝。
方法一:利用JSON.stringify和JSON.parse
let swr = {
name:"邵威儒",
age:28,
pets:['小黄']
}
let swrcopy = JSON.parse(JSON.stringify(swr))
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }
// 此时我们新增swr的属性
swr.pets.push('旺财')
console.log(swr) // { name: '邵威儒', age: 28, pets: [ '小黄', '旺财' ] }
// 但是swrcopy却不会受swr影响
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }
这种方式进行深拷贝,只针对json数据这样的键值对有效
对于函数等等反而无效,不好用,接着继续看方法二、三。
方法二
function deepCopy(fromObj,toObj) { // 深拷贝函数
// 容错
if(fromObj === null) return null // 当fromObj为null
if(fromObj instanceof RegExp) return new RegExp(fromObj) // 当fromObj为正则
if(fromObj instanceof Date) return new Date(fromObj) // 当fromObj为Date
toObj = toObj || {}
for(let key in fromObj){ // 遍历
if(typeof fromObj[key] !== 'object'){ // 是否为对象
toObj[key] = fromObj[key] // 如果为普通值,则直接赋值
}else{
if(fromObj[key] === null){
toObj[key] = null
}else{
toObj[key] = new fromObj[key].constructor // 如果为object,则new这个object指向的构造函数
deepCopy(fromObj[key],toObj[key]) // 递归
}
}
}
return toObj
}
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黄",
sex:"母"
}
]
}
let dogcopy = deepCopy(dog)
// 此时我们把dog的属性进行增加
dog.firends.push({name:"小红",sex:"母"})
console.log(dog) // { name: '小白',
sex: '公',
firends: [ { name: '小黄', sex: '母' }, { name: '小红', sex: '母' } ] }
// 当我们打印dogcopy,会发现dogcopy不会受dog的影响
console.log(dogcopy) // { name: '小白',
sex: '公',
firends: [ { name: '小黄', sex: '母' } ] }
方法三
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黄",
sex:"母"
}
]
}
function deepCopy(obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
let newObj = new obj.constructor
for(let key in obj){
newObj[key] = deepCopy(obj[key])
}
return newObj
}
let dogcopy = deepCopy(dog)
dog.firends.push({name:"小红",sex:"母"})
console.log(dogcopy)
3、声明类与继承:class、extend
简介
在编程的过程中,我们主要运用的有函数编程和面向对象编程,而在一些大型的项目中,大部分使用的都是面向对象编程,而这里也就有了类的继承和声明这些个问题出现。
类的声明
ES5中类的声明
在ES5中类的声明主要是通过构造函数来实现的,在声明过程中,主要是通过使用 this,将
let family = function(name){
this.name = name;
this.parent = function(){
console.log('my parent');
}
}
let son = new family('son')
let mother = new family('mother')
console.log(son); //family{name:"son",parent:f}
console.log(mother); //family{name:"mother",parent:f}
当然在在这个类里,也不是所有的属性跟方法都是挂载在实例化对象里面的,还有一些对于实例对象来说公有的方法或者属性,它是挂载在原型对象上的。
let family = function(name){
this.name = name;
this.parent = function(){
console.log('my parent');
}
}
// 实例对象通过原型链拿到 work 这个方法
family.prototype.work = function(){
console.log('my work');
}
这里可以看出ES5里面想要声明一个类是比较麻烦的,那么在ES6里面,又是怎么做声明的呢
ES6中类的声明
在ES6里面,新增了一个叫做 class 的类的声明的语法糖,它的使用方法是这样的。
class family{
constructor(name){
this.name = name
}
work(){
console.log('my work')
}
let son = new family('son')
console.log(son)
在ES6里面,新增了一个叫做 class 的类的声明的语法糖,它的使用方法是这样的。
class family{
constructor(name){
this.name = name
}
work(){
console.log('my work')
}
let son = new family('son')
console.log(son)
这里,重点说明一个点就是,在使用 class 之后,对于想要将方法挂载在原型对象上的时候,已经不需要跟ES5一样那么麻烦了,只需要将方法直接写在constructor的外面就可以了。
说完了ES6中类的声明,我们再来聊聊ES6中新增的另一个东西,它可以让类的属性变为只可以读取。
ES6中的get和set
当你在声明一个类的时候,有些属性,是不希望别人可以随意的对它进行更改的,也就是把它定义为 私有属性,在ES5的时候基本不可能做到,但是在ES6的时候是可以办到的,而这个就是通过 get 来实现。
let _age = 18
class family {
constructor(name){
this.name = name
}
work(){
console.log('my work')
}
get age() {
return _age
}
}
let son = new family('son')
console.log(son.age); //18
在这里定义一个私有属性的时候,用户访问这个属性的入口,跟这个属性的值存储的位置,是不一样的。
而且这时候的age是不会被修改的,例如
son.age = 20
console.log(son.age); //18
但是,这里的age是不是完全没办法改变呢,其实也不是,如果想要改变它的值,就可以通过ES6中的另一个新东西—— set
它的使用方法跟 get 差不多。
let _age = 18
class family {
constructor(name){
this.name = name
}
work(){
console.log('my work')
}
get age() {
return _age
}
set age(value){
_age = value
}
}
let son = new family('son')
son.age = 20
console.log(son.age); //20
其实本质上来说,它并不是直接改变age的值,而是通过改变_age的值,而间接的改变原age的值,有了这个方法之后,就可以有了新的玩法了,例如
set age(value){
if(value>18 && value<26){
_age = value
}
}
类的静态方法的挂载
一般来说,在构造函数里面的方法,是用来操作实例对象的属性的,或者说它与实例对象的属性有关系,如果在类中一个方法跟实例对象的属性完全无关的时候,就可以将它变为静态方法。
这里有一个需要注意的地方:在实例对象上,是没办法找到类的静态方法的
ES5挂载静态方法
ES5中声明和使用类的静态方法是这样的
let family = function(name){
this.name = name;
this.parent = function(){
console.log('my parent');
family.love()
}
}
family.love = function(){
console.log('I love you ');
}
let son = new family('son')
son.parent();
//my parent I love you
ES6挂载静态方法(static)
在ES6中引入了一个新的东西,就是 static ,它就是用来在ES6中给类挂载静态方法的。
class family {
constructor(name){
this.name = name
}
parent(){
console.log('my parent')
family.love()
}
static love(){
console.log('I love you');
}
}
let son = new family('son')
son.parent();
//my parent I love you
使用static的时候,同样的只需要把方法写在class里面,然后在方法的前面加一个 static 就可以了,在代码的阅读性上有了很大的提高。
class father{
constructor(value){
this.name = value
}
parent(){
console.log('my parent');
}
static love(){
console.log('love')
}
}
father.prototype.parent() //my parent
father.love() //love
类的继承
在面向对象编程的过程中,最强大一个东西,就是类的继承,但是它又不太好理解,因为这里涉及到了原型链的问题,但是在ES6里,类的继承,同样也变的不再复杂。
ES5中类的继承
ES5中类的继承,方法有特别多,为了实现完整的继承,本质上就是使用原型链和构造函数结合起来的方式来继承。
let father = function(value){
this.name = value;
this.parent = function(){
console.log('my parent');
}
}
let children = function(value){
father.call(this,value) //构造函数继承
}
children.prototype = father.prototype //原型链继承
let son = new children('son')
console.log(son); //children {name:"son",parent:f}
这里可以看出来ES5的继承是比较复杂的,需要涉及的面比较多,而且这种继承也不是最完美的写法。
在ES6中,有引入了两个新的东西,用来完善类的继承这个事情,它们就是 extends 和 super。
class father{
constructor(value){
this.name = value
}
parent(){
console.log('my parent');
}
}
class children extends father{
constructor(value){
super(value) //构造函数继承
this.age = 18
}
}
这里也有一个需要注意的地方,就是super()必须写constructor里面的最上面,这里涉及到的一个问题是隐式原型和显式原型的问题,在这篇博文就不做过多的解释了。
4、Promise 的使用与实现(原理)
Promise标准
-
Promise
规范有很多,如Promise/A
,Promise/B
,Promise/D
以及Promise/A
的升级版,Promise/A+
。ES6 中采用了Promise/A+
规范。
英文版规范:Promises/A+规范
中文版规范:Promises/A+规范(中文) -
Promise
标准解读
(1) 一个promise
的当前状态只能是pending
、fulfilled
和rejected
三种之一。状态改变只能是pending
到fulfilled
或者pending
到rejected
。状态改变不可逆。
(2)promise
的then
方法接收两个可选参数,表示该promise
状态改变时的回调(promise.then(onFulfilled, onRejected)
)。then
方法返回一个promise
。then
方法可以被同一个promise
调用多次。
实现Promise
Promise
标准仅描述了then
方法的行为,未对catch、all、race
方法进行描述,也未规范如何创建一个Promise
对象。ES6中Promise
提供了以下 API
。
(1) 构造函数
function Promise(resolver) {}
(2) 原型方法
Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
(3) 静态方法
Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
构造函数
Promise/A+
标准并没有指定如何构造一个Promise
对象,我们以ES6原生Promise模块里通过构造函数创建Promise
对象的方式实现Promise
构造函数。ES6中通过构造函数创建Promise
对象的简单用法如下:
const promise = new Promise((resolve) => {
setTimeout(()=> {
resolve(1);
}, 2000);
});
promise.then(a=> alert(a));
promise.then(a => alert(a+1));
构造函数用法总结:
(1) 构造函数接收一个executor立即执行函数
(2) executor立即执行函数接收一个resolve函数
(3) promise对象的then方法绑定状态变为fulfilled时的回调
(4) resolve函数被调用时会触发then方法中的回调
构造函数的简单实现
function Promise(executor) {
var self = this;
self.status = 'pending'; //promise当前的状态
self.data = undefined; //promise的值
self.onResolvedCallback = [];
//promise状态变为resolve时的回调函数集,可能有多个
function resolve(value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
}
executor(resolve);
};
Promise.prototype.then = function (resolve) {
this.onResolvedCallback.push(resolve);
};
executor自执行函数接收的第二个参数为reject函数且reject函数在promise对象状态变为rejected时或executor抛出异常时触发。
function Promise(executor) {
var self = this;
self.status = 'pending'; //promise当前的状态
self.data = undefined; //promise的值
self.onResolvedCallback = [];
//promise状态变为resolve时的回调函数集,可能有多个
self.onRejectedCallback = [];
//promise状态变为reject时的回调函数集,可能有多个
function resolve(value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason);
}
}
}
try {
executor(resolve, reject);
} catch (e){
reject(e);
}
};
Promise.prototype.then = function (onResolve, onReject) {
this.onResolvedCallback.push(onResolve);
this.onRejectedCallback.push(onReject);
};
总结:①executor函数作为实参在创建Promise对象时传入Promise构造函数。②resolve和reject函数作为实参传入executor函数。③value作为实参传入resolve和reject函数。
如果executor自执行函数中的resolve函数立即触发时,发现Promise失效,例如:
const promise = new Promise((resolve) => {
resolve(1);
});
promise.then((a) => alert(a));
解决办法:需要将promise的resolve和reject异步执行。
function resolve(value) {
setTimeout(function () {
if(self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
})
}
function reject(reason) {
setTimeout(function () {
if(self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason);
}
}
})
}
then方法
- 在构造函数中的示例中,then方法并没有返回一个promise对象,而 Promise/A+ 规范中规定then方法用来注册promise对象状态改变时的回调,且返回一个新的promise对象。
(1)then方法返回一个新的promise对象。
(2)executor自执行函数中的resolve参数调用时执行then方法的第一个回调函数onResolved。
(3)executor自执行函数中的reject参数调用时执行then方法的第二个回调函数onRejected。
Promise.prototype.then = function (onResolved, onRejected) {
var self = this;
var promise2;
onResolved = typeof onResolved === 'function'
? onResolved
: function (value) {return value};
onRejected = typeof onRejected === 'function'
? onRejected
: function (reason) {throw reason};
//promise对象当前状态为resolved
if(self.status === 'resolved') {
return promise2 = new Promise(function (resolve, reject) {
try {
//调用onResolve回调函数
var x = onResolved(self.data);
//如果onResolve回调函数返回值为一个promise对象
if(x instanceof Promise) {
//将它的结果作为promise2的结果
x.then(resolve, reject);
} else {
resolve(x);//执行promise2的onResolve回调
}
} catch (e) {
reject(e); //执行promise2的onReject回调
}
})
}
//promise对象当前状态为rejected
if(self.status === 'rejected') {
return promise2 = new Promise(function (resolve, reject) {
try {
var x = onRejected(self.data);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
})
}
//promise对象当前状态为pending
//此时并不能确定调用onResolved还是onRejected,需要等当前Promise状态确定。
//所以需要将callBack放入promise1的回调数组中
if(self.status === 'pending') {
return promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallback.push(function (value) {
try {
var x = onResolved(self.data);
if (x instanceof Promise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x);
}
} catch (e) {
reject(e)
}
})
})
}
};
参照Promise/A+标准对promise进行改写
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 异步执行onResolved
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 异步执行onRejected
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'pending') {
// 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
}
完整代码
var Promise = (function() {
function Promise(resolver) {
if (typeof resolver !== 'function') { //resolver必须是函数
throw new TypeError('Promise resolver ' + resolver + ' is not a function')
}
if (!(this instanceof Promise)) return new Promise(resolver)
var self = this //保存this
self.callbacks = [] //保存onResolve和onReject函数集合
self.status = 'pending' //当前状态
function resolve(value) {
setTimeout(function() { //异步调用
if (self.status !== 'pending') {
return
}
self.status = 'resolved' //修改状态
self.data = value
for (var i = 0; i < self.callbacks.length; i++) {
self.callbacks[i].onResolved(value)
}
})
}
function reject(reason) {
setTimeout(function(){ //异步调用
if (self.status !== 'pending') {
return
}
self.status = 'rejected' //修改状态
self.data = reason
for (var i = 0; i < self.callbacks.length; i++) {
self.callbacks[i].onRejected(reason)
}
})
}
try{
resolver(resolve, reject) //执行resolver函数
} catch(e) {
reject(e)
}
}
function resolvePromise(promise, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
return resolve(x)
}
} catch(e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
return resolve(x)
}
}
Promise.prototype.then = function(onResolved, onRejected) {
//健壮性处理,处理点击穿透
onResolved = typeof onResolved === 'function' ? onResolved : function(v){return v}
onRejected = typeof onRejected === 'function' ? onRejected : function(r){throw r}
var self = this
var promise2
//promise状态为resolved
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
//调用then方法的onResolved回调
var x = onResolved(self.data)
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
//promise2状态变为rejected
return reject(e)
}
})
})
}
//promise状态为rejected
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
//调用then方法的onReject回调
var x = onRejected(self.data)
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
//promise2状态变为rejected
return reject(e)
}
})
})
}
//promise状态为pending
//需要等待promise的状态改变
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
self.callbacks.push({
onResolved: function(value) {
try {
//调用then方法的onResolved回调
var x = onResolved(value)
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
//promise2状态变为rejected
return reject(e)
}
},
onRejected: function(reason) {
try {
//调用then方法的onResolved回调
var x = onRejected(reason)
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
//promise2状态变为rejected
return reject(e)
}
}
})
})
}
}
//获取当前Promise传递的值
Promise.prototype.valueOf = function() {
return this.data
}
//由then方法实现catch方法
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
//finally方法
Promise.prototype.finally = function(fn) {
return this.then(function(v){
setTimeout(fn)
return v
}, function(r){
setTimeout(fn)
throw r
})
}
Promise.prototype.spread = function(fn, onRejected) {
return this.then(function(values) {
return fn.apply(null, values)
}, onRejected)
}
Promise.prototype.inject = function(fn, onRejected) {
return this.then(function(v) {
return fn.apply(null, fn.toString().match(/\((.*?)\)/)[1].split(',').map(function(key){
return v[key];
}))
}, onRejected)
}
Promise.prototype.delay = function(duration) {
return this.then(function(value) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(value)
}, duration)
})
}, function(reason) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(reason)
}, duration)
})
})
}
Promise.all = function(promises) {
return new Promise(function(resolve, reject) {
var resolvedCounter = 0
var promiseNum = promises.length
var resolvedValues = new Array(promiseNum)
for (var i = 0; i < promiseNum; i++) {
(function(i) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function(reason) {
return reject(reason)
})
})(i)
}
})
}
Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function(value) {
return resolve(value)
}, function(reason) {
return reject(reason)
})
}
})
}
Promise.resolve = function(value) {
var promise = new Promise(function(resolve, reject) {
resolvePromise(promise, value, resolve, reject)
})
return promise
}
Promise.reject = function(reason) {
return new Promise(function(resolve, reject) {
reject(reason)
})
}
Promise.fcall = function(fn){
// 虽然fn可以接收到上一层then里传来的参数,但是其实是undefined,所以跟没有是一样的,因为resolve没参数啊
return Promise.resolve().then(fn)
}
Promise.done = Promise.stop = function(){
return new Promise(function(){})
}
Promise.deferred = Promise.defer = function() {
var dfd = {}
dfd.promise = new Promise(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
try { // CommonJS compliance
module.exports = Promise
} catch(e) {}
return Promise
})()
5、generator(异步编程、yield、next()、await 、async)
6、箭头函数 this 指向问题、拓展运算符
一、扩展运算符
二、箭头函数指向问题
箭头函数中的this
箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
要整明白这些, 我们需要首先了解一下作用域链:
当在函数中使用一个变量的时候,首先在本函数内部查找该变量,如果找不到则找其父级函数,
最后直到window,全局变量默认挂载在window对象下
7、module
8、map和set有没有用过,如何实现一个数组去重,map数据结构有什么优点?
set结构不会添加重复的值。
Array.from方法可以将Set结构转为数组,这就提供了去除数组重复成员的方法
function dedupe(array){
return Array.from(new Set(array));
}
dedupe([1,1,2,3,2,4,4]);
//1,2,3,4
Map结构:javascript的对象,本质上是键值对的集合,但是传统上只能用字符串当做键。而Map数据结构的键的范围不限于字符串,各种类型
9、ES6 怎么编译成 ES5,css-loader 原理,过程
Babel
是一个 JavaScript 编译器
Babel
是一个工具链,主要用于将 ECMAScript 2015+
版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
css-loader原理,过程
loader
用于对模块的源代码进行转换。loader
可以使你在 import 或"加载"模块时预处理文件。因此,loader
类似于其他构建工具中“任务(task)”
,并提供了处理前端构建步骤的强大方法。
css-loader
是分析各个css
文件的关系并合并成一个css
。
Babel使用指南
关于es6及以上的js编译成es5
10、ES6 转成 ES5 的常见例子
webpack 配合babel 将es6转成es5 超简单实例