ES6主要相关问题重学
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
概念
解构赋值主要分为对象的解构和数组的解构,在没有解构赋值的时候,我们赋值是这样的
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)
简介
在编程的过程中,我们主要运用的有函数编程和面向对象编程,而在一些大型的项目中,大部分使用的都是面向对象编程,而这里也就有了类的继承和声明这些个问题出现。
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里面的最上面,这里涉及到的一个问题是隐式原型和显式原型的问题,在这篇博文就不做过多的解释了。
隐式原型和显示原型的联系
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
标准仅描述了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方法
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
})()
箭头函数中的this
箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
要整明白这些, 我们需要首先了解一下作用域链:
当在函数中使用一个变量的时候,首先在本函数内部查找该变量,如果找不到则找其父级函数,
最后直到window,全局变量默认挂载在window对象下
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数据结构的键的范围不限于字符串,各种类型
Babel
是一个 JavaScript 编译器
Babel
是一个工具链,主要用于将 ECMAScript 2015+
版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
css-loader原理,过程
loader
用于对模块的源代码进行转换。loader
可以使你在 import 或"加载"模块时预处理文件。因此,loader
类似于其他构建工具中“任务(task)”
,并提供了处理前端构建步骤的强大方法。
css-loader
是分析各个css
文件的关系并合并成一个css
。
Babel使用指南
关于es6及以上的js编译成es5
webpack 配合babel 将es6转成es5 超简单实例
使用 es5 实现 es6 的 class