转载请注明出处javascript
原文链接 http://blog.huanghanlian.com/article/5c7aa6c7bf3acc0864870f9dhtml
首先弄明白ECMA和js的关系。ECMA是标准,Javascript是ECMA的实现。由于js也是一种语言,但凡语言都有一套标准,而ECMA就是javascript的标准。 在2015年正式发布了ECMAscript6.0,简称ES6,又称为ECMAscript2015。前端
时间 | ECMA | JS | 解释 |
---|---|---|---|
1996.11 | EC 1.0 | JS稳定 | Netscript将js提交给ECMA组织,ES正式出现 |
1998.06 | ES 2.0 | ES2正式发布 | |
1999.12 | ES 3.0 | ES3被普遍接受 | |
2007.10 | ES 4.0 | ES4过于激进,被废了 | |
2008.07 | ES 3.1 | 4.0退化为严重缩水版的3.1 由于吵得太厉害,因此ES3.1代号为Harmony(和谐) |
|
2009.12 | ES 5.0 | ES5正式发布 同时公布了JavaScript.next也就是后来的ES6.0 |
|
2011.06 | ES 5.1 | ES5.1成为了ISO国际标准 | |
2013.03 | ES 6.0 | ES6.0草案定稿 | |
2013.12 | ES 6.0 | ES6.0草案发布 | |
2015.06 | ES 6.0 | ES6.0预计发布正式版 JavaScript.next开始指向ES 7.0 |
es5兼容性java
http://kangax.github.io/compat-table/es5/node
es6兼容性react
http://kangax.github.io/compat-table/es6/git
ES6(ES2015)-- IE10+,Chrome,FireFox,移动端,NodeJS。这些环境基本上都是认得,都能兼容es6
可是有需求兼容ie怎么办github
有两种办法ajax
比方说在移动端或者是混合开发当中,多去用用ES6,在老的版本中不用。惹不起咋躲得起。
编译,转换
ES6的到底有什么样的东西?
回顾ES5是怎么生明变量的,有什么样的缺点
var
的缺点
能够重复声明
最大的问题
var a=12; var a=5; alert(a);//弹窗5
会发现5能出来,没有报错,没有警告,什么都没有
这在其余语言是不可出现的。
没法限制修改
在程序中,有些东西是永远不变的。
比方说常量
PI=3.1415926
是不会发生改变
在不少语言中都有常量
的概念。在js中没有
至少var
不是一个常量。
换句话说,要不要改,能不能让别人别动这个值,不要改这个值。全凭自觉。
为何java是全世界最流行的一门语言
缘由很简单,由于他很是的严谨,他很是的死板。
相信一件事,越是容易的语言,越是简单的语言。其实是不严谨。就无法去开发大型项目
反过来他可能让你以为很难受的语言java,对你限制很严格。可是你掌握了呀以后,开发起大型应用会很是的驾轻就熟。
没有块级做用域
es5 只在函数中支持块级做用域
{ //这就是语法块 } if(){ 变量=xxxx } //变量出来就用不了了,这就是块做用域 for(){ }
体现块级做用域做用
if(true){ var a=12; } alert(a); //在块级做用域内声明变量。在外部依然可以访问
在ES6中有了两种新的定义变量的方式
let
和const
let
const
let 不能重复声明例子
let a=12; let a=5; console.log(a); //报错 //Uncaught SyntaxError: Identifier 'a' has already been declared //不能重复声明
const 不能重复声明例子
const a=12; const a=5; console.log(a); //报错 //Uncaught SyntaxError: Identifier 'a' has already been declared //不能重复声明
在大型项目中,重复声明这件事,指不定你定义了什么东西别人也定义了。还不报错,到时候定位bug很难找。
变量和常量
变量
let a=12;//声明变量赋值 a=5;//给变量a赋值 console.log(a);//你会明确的发现它变成了5
常量
const a=12; a=5; console.log(a);
报错,不能对常量赋值
块级做用域
var 块级做用域只在函数中体现,也就是说在函数中var
声明的变量不会在全局做用域中体现
function aa(){ var a=1; console.log(a) } aa(); console.log(a)
let和const只在块级做用域,或者在语法块以内起做用
if(true){ let a=12; } console.log(a);//Uncaught ReferenceError: a is not defined
if(true){ const a=12; } console.log(a);//Uncaught ReferenceError: a is not defined
语言推出一个新的版本,一个更好的版本,他必定是要解决一些原来有的问题,ES6也不例外。
块级做用域有什么
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按钮1"> <input type="button" value="按钮2"> <input type="button" value="按钮3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (var i = 0; i < aBtn.length; i++) { aBtn[i].onclick=function(){ alert(i) }; } }; </script> </html>
以上代码执行,无论按哪一个按钮弹出都是3
因为var
声明变量只在函数做用域中扩散到全局
在for或者if快级做用域中声明的变量会在局部或全局生效
当for
循环执行完毕,i
这个变量暴露到全局了,等于3
因此for
循环中执行的事件绑定,是将点击事件回调函数执行。当点击按钮时候,会出发绑定回调函数,此时当前做用域中,i
等于3,因此不管点击哪一个按钮弹出都是3
之前咱们是经过闭包解决这个问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按钮1"> <input type="button" value="按钮2"> <input type="button" value="按钮3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (var i = 0; i < aBtn.length; i++) { ! function(i) { aBtn[i].onclick=function(){ alert(i) }; }(i); } console.log(i) }; </script> </html>
在每一层循环的时候,用一个匿名函数并且是当即执行的匿名函数给他包装起来,而后将每一次遍历的1.2.3分别的值去传到这个匿名函数里,而后匿名函数接到这个参数i再放到点击事件中去引用i当咱们每次点击事件输出的值i就会取每个闭包环境下的i。因此这样就能达到效果。
使用let
来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按钮1"> <input type="button" value="按钮2"> <input type="button" value="按钮3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (let i = 0; i < aBtn.length; i++) { aBtn[i].onclick=function(){ alert(i) }; } console.log(i) }; </script> </html>
for
循环自己就是一个语法块,自身就是一个块
因为var
只把函数做为做用域
因此以上须要经过当即执行函数来包一层,来实现效果。
而let
自己是支持块级做用域的,因此电脑按钮执行回掉函数,打印i,是当前块级做用域下的i
这个i
在非for
块做用域下是未定义的。
箭头函数在写法上对es5作了一些修整,代码看起来更显得简洁
// 定义一个箭头函数 let a = (arg)=>{ // 这里=>符号就至关于function关键字 return arg+=1 } // 也能够简写为 let a = arg => arg+=1
箭头函数的做用跟之前接触的函数没什么本质的区别,更多的是一种写法上的变化。
function show() { } 同等于 let show =()=>{ }
function () { } 同等于 ()=>{ }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> </body> <script type="text/javascript"> window.onload=function(){ alert('123') }; </script> </html> 同等于 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> </body> <script type="text/javascript"> window.onload=()=>{ alert('123') }; </script> </html>
箭头函数也对this的指向作了修整 es6以前的函数的this指向调用函数时所在的对象,而箭头函数的this指向函数定义时所在的对象
//普通函数 var obj = { say: function () { setTimeout(function() { console.log(this) }); } } obj.say();//Window object
// 箭头函数 var obj = { say: function () { setTimeout(() => { console.log(this) }); }, test:123 } obj.say(); // obj
参数扩展
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不须要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function show (a,b,...args){ console.log(a);//1 console.log(b);//2 console.log(args);//[3,4,5,6] } show(1,2,3,4,5,6);
下面是一个 rest 参数代替arguments变量的例子。
// arguments变量的写法 function sortNumbers() { return Array.prototype.slice.call(arguments).sort(); } // rest参数的写法 const sortNumbers = (...numbers) => numbers.sort();
展开后的效果,跟直接把数组内容写在这同样
let arr=[1,2,3]; console.log(1,2,3); console.log(...arr); //1,2,3同等于...arr
function show(a,b,c){ console.log(a) console.log(b) console.log(c) } let arr=[1,2,3]; show(1,2,3); show(...arr); //1,2,3同等于...arr
let arr1=[1,2,3]; let arr2=[4,5,6]; let arr=[...arr1,...arr2]; let arrS=[1,2,3,4,5,6]; //...arr1,写法,至关于将数组内容掏出来内容
默认参数
function show(a,b=5,c=6){ //我但愿b,默认是5 不传的时候 //我但愿c,默认是6 不传的时候 console.log(a,b,c);//1,2,6 } show(1,2);
容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。好比:
var [a,b] = [1,2] // a=1 b=2
let [a, b] = [1, 2] // 左右都是数组,能够解构赋值 let {a, b} = {a:1, b:2} // 左右都是对象,能够解构赋值 let [obj, arr] = [{a:1}, [1, 2]] // 左右都是对象,能够解构赋值 let [a, b] = {a:1, b:2} // err 左右结构不同,不能够解构赋值 let {a,b} = {1, 2} // err 右边不是一个合法的数据,不能解构赋值 let [a, b]; [a, b] = [1, 2] // err 声明与赋值分开,不能解构赋值
数组扩展了4个方法:map、reduce、filter、forEach
经过指定函数处理数组的每一个元素,并返回处理后的数组。
一个对一个
[12,58,99,86,45,91] [不及格,不及格,及格,及格,不及格,及格] //将数组映射成另外一个数组
[45,57,135,28] //将用户id映射成对象 [ {name:'huang',role:1}, {name:'huang1',role:2}, {name:'huang2',role:3}, {name:'huang4',role:1} ]
map例子
let arr=[12,5,8]; //我想要将数组内容乘与2的结果 let result=arr.map(function(item){ console.log(item); //须要将你要的内容返回出来 return item*2; }); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
简写1
let arr=[12,5,8]; //我想要将数组内容乘与2的结果 let result=arr.map(item=>{ console.log(item); //须要将你要的内容返回出来 return item*2; }); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
简写2
let arr=[12,5,8]; //我想要将数组内容乘与2的结果 let result=arr.map(item=>item*2); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
let arr=[12,58,99,86,45,91] let result=arr.map(item=>item>=60?'及格':'不及格'); console.log(arr);//[12, 58, 99, 86, 45, 91] console.log(result);//["不及格", "不及格", "及格", "及格", "不及格", "及格"]
将数组元素计算为一个值(从左到右)。
一堆出一个
算个总数
let arr=[12,58,99,86,45,91] /** * [description] * @param {[type]} (total,currentValue,index,arr [ * 初始值, 或者计算结束后的返回值。 * 当前元素 * 当前元素的索引 * 当前元素所属的数组对象。 * ] * @return {[type]} [返回计算结果] */ let result=arr.reduce((total,currentValue,index,arr)=>{ return total+currentValue; }); console.log(result)//391
算个平均数
let arr=[12,58,99,86,45,91] /** * [description] * @param {[type]} (total,currentValue,index,arr [ * 初始值, 或者计算结束后的返回值。 * 当前元素 * 当前元素的索引 * 当前元素所属的数组对象。 * ] * @return {[type]} [返回计算结果] */ let result=arr.reduce((total,currentValue,index,arr)=>{ if(index!=arr.length-1){ //若是不是最后一次 return total+currentValue; //求和 }else{ //最后一次 return (total+currentValue)/arr.length; //求和再除于长度个数 } }); console.log(result)//65.16666666666667
检测数值元素,并返回符合条件全部元素的数组。
定义和用法
filter() 方法建立一个新的数组,新数组中的元素是经过检查指定数组中符合条件的全部元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
需求,能被3整除的留下,不能的去除
let arr=[12,58,99,86,45,91] //需求,能被3整除的留下,不能的去除 /** * [description] * @param {[type]} (currentValue,index,arr [ * 当前元素的值 * 当前元素的索引值 * 当前元素属于的数组对象 * ] * @return {[type]} [返回数组,包含了符合条件的全部元素。若是没有符合条件的元素则返回空数组。] */ let result=arr.filter((currentValue,index,arr)=>{ if(currentValue%3==0){ return true; }else{ return false; } }); console.log(result)//[12, 99, 45]
简写
let arr=[12,58,99,86,45,91] //需求,能被3整除的留下,不能的去除 /** * [description] * @param {[type]} (currentValue,index,arr [ * 当前元素的值 * 当前元素的索引值 * 当前元素属于的数组对象 * ] * @return {[type]} [返回数组,包含了符合条件的全部元素。若是没有符合条件的元素则返回空数组。] */ let result=arr.filter((currentValue,index,arr)=>{ return currentValue%3==0; }); console.log(result)//[12, 99, 45]
再次简写
let arr=[12,58,99,86,45,91] let result=arr.filter(currentValue=>currentValue%3==0); console.log(result)//[12, 99, 45]
数组每一个元素都执行一次回调函数。
forEach() 方法用于调用数组的每一个元素,并将元素传递给回调函数。
注意: forEach() 对于空数组是不会执行回调函数的。
多了两个新方法
startsWith应用
let str='http://blog.huanghanlian.com' if(str.startsWith('http://')){ console.log('普通网址'); }else if(str.startsWith('https://')){ console.log('加密网址'); }else if(str.startsWith('git://')){ console.log('git网址'); }else if(str.startsWith('svn://')){ console.log('svn网址'); }else{ console.log('其余') }
let str='http://blog.huanghanlian.com/sitemap.xml' if(str.endsWith('.xml')){ console.log('网站地图'); }else if(str.endsWith('.jpg')){ console.log('图片'); }else if(str.endsWith('.txt')){ console.log('文本文件'); }else{ console.log('其余') } //网站地图
字符串模板
模板字符串有两个能力
日常写字符串有两种写法,
let str="abc"; let str2='efg';
一种是单引号,一种是双引号。在js里都能用。没什么区别
如今出来一种新的字符串
let str=`abc`;
这种符号叫反单引号
简单使用例子
let a=12; let str=`a${a}bc`; console.log(str);//a12bc
反单引号中的美圆符号带上花括号他的做用就是把变量直接塞进字符串里面去。
例子
之前字符串拼接
let title='我是标题'; let content='我是内容'; let str='<div class="wrap">\ <h1>'+title+'</h1>\ <p>'+content+'</p>\ </div>'; document.body.innerHTML=str; console.log(str);
有了字符串模板后的写法
let title='我是标题'; let content='我是内容'; let str='<div class="wrap">\ <h1>'+title+'</h1>\ <p>'+content+'</p>\ </div>'; let str2=`<div class="wrap"> <h1>${title}</h1> <p>${content}</p> </div>`; document.body.innerHTML=str2; console.log(str2);
面向对象
es5面向对象
function User(name,pass){ this.name=name; this.pass=pass; } //给这个类加原型方法 /** * [showName 获取用户名] * @return {[type]} [返回用户名] */ User.prototype.showName=function(){ console.log(this.name); return this.name; }; /** * [showPass 获取用户密码] * @return {[type]} [返回用户密码] */ User.prototype.showPass=function(){ console.log(this.pass); return this.pass; }; var ul=new User('黄继鹏','abc'); //调用类方法 ul.showName();//黄继鹏
这样写的缺点
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,做为对象的模板。经过class关键字,能够定义类。 先看如何定义一个class类:
class User { constructor(name) { // 构造器,至关于es5中的构造函数 this.name = name // 实例属性 } showName(){ // 定义类的方法,不能使用function关键字,不能使用逗号分隔 console.log(this.name) } } var foo = new User('黄继鹏') foo.showName();//黄继鹏
其实class的基本类型就是函数(typeof User = "function"),既然是函数,那么就会有prototype属性。
类的全部方法都是定义在prototype上
class User { constructor() { // ... } toString() { // ... } toValue() { // ... } } User.toValue() // err User.toValue is not a function User.prototype.toValue() // 能够调用toValue方法 // 等同于 User.prototype = { constructor() {}, toString() {}, toValue() {}, };
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); point.toString() // (2, 3) point.hasOwnProperty('x') // true point.hasOwnProperty('y') // true point.hasOwnProperty('toString') // false point.proto.hasOwnProperty('toString') // true
若是在类中定义一个方法的前面加上static关键字,就表示定义一个静态方法,静态方法不会被实例继承,但会被子类继承,因此不能经过实例使用静态方法,而是经过类直接调用
class User { constructor(name){ this.name = name } static show(){ console.log('123') } } class VipUser extends User{} VipUser.show() // 123 User.show() // 123 var foo = new User('foo') foo.show() // foo.show is not a function
class User{} User.name = 'foo' // 为class定义一个静态属性 class VipUser extends User{} console.log(VipUser.name) // foo var foo = new User() console.log(foo.name) // undefined
es6是不支持私有属性和私有方法,可是平常需求可能会用到私有属性和私有方法,因此目前有一些提案,不过只是提案,还没有支持。
类的继承
ES5写法
function User(name,pass){ this.name=name; this.pass=pass; } User.prototype.showName=function(){ console.log(this.name); }; /** * [showPass 获取用户密码] * @return {[type]} [返回用户密码] */ User.prototype.showPass=function(){ console.log(this.pass); }; //继承user类 function aUser(name, pass, type) { User.call(this, name, pass); this.type = type; }; aUser.prototype.showType = function() { console.log(this.type); }; var ul=new User('黄继鹏','abc'); ul.showName()//黄继鹏 var ull=new aUser('继小鹏','ccc','男'); ul.showName();//继小鹏 ull.showType();//男 //aUser继承类User类,而且有本身的方法
class经过extends关键字实现继承:
class User { constructor(name){ this.name = name } show(){...} } class VipUser extends User{ constructor(vipName){ // 子类的构造器 super(vipName) // 调用父类的constructor。至关于User.prototype.constructor.call(this,vipName) } showVip(){...} } var v = new VipUser('foo') // 建立实例 v instanceof VipUser // v是子类VipUser的实例 v instanceof User // v仍是父类User的实例
super能够当作函数使用,也能够当作对象使用。
当作函数使用
super做为函数调用时,表明父类的构造函数,就是在子类的构造器中执行父类的constructor函数以获取父类的this对象,由于子类没有本身的this对象,因此ES6规定子类必须在constructor中执行一次super函数。super()函数只能在子类的constructor中执行,不能在其余地方执行。
虽然super表明父类的构造器,可是super()在执行时内部的this指向子类,因此super()就至关于User.prototype.constructor.call(this)。
当作对象使用
super能够做为对象调用父类的属性和方法,在子类的普通方法中,指向父类的原型对象(即User.prototype);在子类的静态方法中,指向父类。
class User { constructor(){ this.x = 'hello' } show() { return 2; } } class VipUser extends User { constructor() { super(); console.log(super.show()); // 2 此时super指向User.prototype,至关于User.prototype.show() console.log(super.x) // undefined 没法访问实例属性 } } let vip = new VipUser(); console.log(vip.x);//hello
因为super对象在普通函数中使用super指向User.prototype,因此super只能访问父类的原型上的方法,无法访问父类的实例属性和实例方法。
ES6规定若是在子类中使用super对象调用父类的方法时,方法内部的this指向子类
class User { constructor() { this.x = 1 } show() { return this.x; } } class VipUser extends User { constructor() { super(); this.x = 2 console.log(super.show()) // 2 此时show()方法内部的this指向子类,因此输出2,而不是1 } } let vip = new VipUser();
上述代码中虽然super.show()调用的是User.prototype.show(),可是因为经过super对象调用父类方法时,方法内部的this指向子类,因此super.show()至关于 super.show().call(this),也就是User.prototype.show().call(this)
在子类的静态方法中super对象指向父类,而不是父类的原型(User.prototype)。
class User { constructor() { this.x = 1 } static fn() { console.log('父类静态方法') } } class VipUser extends User { constructor() { super(); this.x = 2 } static childFn() { super.fn() // 至关于User.fn() } } VipUser.childFn()
在es5中每个对象都有proto属性,指向对应的构造函数的prototype属性。Class 做为构造函数的语法糖,同时有prototype属性和proto属性,所以同时存在两条继承链。
class User { } class VipUser extends User { } VipUser.proto === User // true VipUser.prototype.proto === User.prototype // true
子类实例的proto属性指向子类的原型(子类的prototype),子类实例的proto属性的proto属性指向父类的原型(父类的prototype)
class User { } class VipUser extends User { } var vip = new VipUser() console.log(vip.proto === VipUser.prototype) // true console.log(vip.proto.proto === User.prototype) // true
面向对象应用---react
react介绍:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> let oDiv=document.getElementById('div1'); ReactDOM.render( <span>123</span>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
ReactDOM.render( <span>123</span>, oDiv );
这种语法为何会支持呢?
这个就是jsx和普通js最大的差异。
你能够认为jsx是普通js的扩展版本
既然是扩展版本,那确定会多出一些功能来。
若是不写引号,不是字符串同时长得像html,他就是能够要建立一个标签
切换搭配重点
react是一个基于组件
组件与class形式存在
我想写一个组件,做为一个组件是否是应该有一些基本的功能,好比我能被渲染,我有一些状态,我有生命周期,换句话说我如今不是从零开始写一个class,我须要不少基础的类的集成。
class Test extends React.Component{ }
类继承最大的意义在于一切不用从零开始
一个类须要有构造函数constructor
.
做为继承类,在构造函数中须要继承父级的属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> class Test extends React.Component{ //构造函数接到任何参数,都给继承父级类 constructor(...args){ super(...args); } render(){ return <div>hello world</div> } } let oDiv=document.getElementById('div1'); ReactDOM.render( <Test>123</Test>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
组件套组件方法例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> class Item extends React.Component{ //构造函数接到任何参数,都给继承父级类 constructor(...args){ super(...args); } render(){ return <div>{this.props.str}</div> } } class List extends React.Component{ //构造函数接到任何参数,都给继承父级类 constructor(...args){ super(...args); } render(){ /*//写法1 let aItem=[]; for(let i=0;i<this.props.arr.length;i++){ aItem.push(<Item key={i} str={this.props.arr[i]}></Item>); }*/ /*// 写法2 let aItem=this.props.arr.map((str,index)=>{ return <Item key={index} str={str}></Item> }) return <div> {aItem} </div>*/ // 写法3 return <div> { this.props.arr.map((str,index)=>{ return <Item key={index} str={str}></Item> }) } </div> } } let oDiv=document.getElementById('div1'); ReactDOM.render( <List arr={['abc','efg','hij']}></List>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
Promise的中文含义是承诺
了解Promise以前。先来了解下同步异步
异步:操做之间没啥管系,同时进行多个操做
同步:同时只能作一件事
同步异步的优缺点
异步:代码更复杂
同步:代码简单
一个页面可能会有多个请求
好比淘宝网页,banner区域,侧边栏,导航栏,右侧栏,信息商品等
都是由镀铬接口异步请求组成
这就回形成代码逻辑复杂
按照以往前端ajax请求写法。一个请求成功后继续请求嵌套。逻辑会变得异常费劲
异步
$.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/1', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功') }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, })
同步
let banner_data=ajax_async('/banner'); let banner_data1=ajax_async('/banner1'); let banner_data2=ajax_async('/banner2'); let banner_data3=ajax_async('/banner3'); let banner_data4=ajax_async('/banner4');
你会发现异步处理性能好,用户体验好,但实际代码复杂
要是同步方式页面用户体验很差
这个时候幻想一下,我能不能像同步方式来写代码。也像异步同样请求数据。
Promise就能作到这个工做
Promise--消除异步操做
Promise如何使用
须要使用promise的时候,你须要new一个promise对象。
这个对象接收一个参数,是一个函数。
将异步的代码写在函数里
这个函数两个参数
resolve
决心
reject
拒绝
//封装Promise ajax let p=new Promise(function(resolve,reject){ //异步代码块 //resolve--成功了 //reject--失败了 $.ajax({ type: 'post', dataType:'json', url: '/api/banner', success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); //使用Promise ajax封装 //当Promise调用有结果了就会调用then //then有两个参数,都是函数,第一个是resolve,第二个是reject p.then((result)=>{ console.log(result); },(error)=>{ console.log(error); })
function createPromise(url){ return new Promise(function(resolve,reject){ //异步代码块 //resolve--成功了 //reject--失败了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } createPromise('./aa') .then((res)=>{ console.log(res) },(err)=>{ console.log(err) })
function createPromise(url){ return new Promise(function(resolve,reject){ //异步代码块 //resolve--成功了 //reject--失败了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } Promise.all([ createPromise('./aa'), createPromise('./bb') ]) .then((res)=>{ let [arr1,arr2]=res },(err)=>{ console.log(err) })
generator的做用
generator-生成器
生成器是程序里面的一个概念,能够依靠它生成一堆东西
Generator能够理解为生成器,和普通函数没多大区别,普通函数是只要开始执行,就一直执行到底,而Generator函数是中间能够停,搭配使用next函数继续执行。
生动的比喻
普通函数比如坐飞机,飞机起飞,不到目的地中途是不会降落的
Generator比如于出租车。能够随叫随停。停了再走,走了再停
function * fn(){ alert('a') yield alert('b') } var f = fn() f.next() // a f.next() // b
直接调用Generator函数,是什么都不执行的,调用第一个next()才开始执行,一直执行到第一个yield中止,第二次调用next(),从第一个yield执行到第二个yield中止,依次类推
如今疑惑,在真实场景中,我为何要让一个函数停呢?
刚才举个了出租车的例子,说白了,你为何要让出租车司机停车,确定是你有事情,你要去忙,或者要求拿什么东西,或者见什么朋友。
等你事情办完了,还再回来。
因此Generator特别适合一个场景。
好比说你要请求数据。请求数据不是瞬间就能回来的,这个时候就须要暂停,来等他结果过来。再继续执行下面的操做。
/** * 普通函数在执行过程当中须要请求获得结果再执行对应代码,就会出现代码嵌套再嵌套 */ function 函数(){ 代码... ajax({ 代码... }) } /** * Generator函数可让代码在那一步暂时暂停 拿到数据后再继续往下走 */ function *函数(){ 代码... yiels ajax(xxx) 代码... }
Generator是怎么作到走走停停的?
其实本质是用Generator函数生成了一堆小函数
比方说fn函数
function * fn(){ alert('a') yield alert('b') } var f = fn() f.next() // a f.next() // b
其实他在背后生成了两个小函数
function fn_1(){ alert('a') } function fn_2(){ alert('b') }
固然这个过程咱们是看不见的
至关于把一个大函数切分红了两个小函数
第一次next的时候他走的是fn_1
第二次next的时候走的是fn_2
yield和next
yield表明暂时暂停执行,next表明继续执行。
yield和next能够传参数,也能够有返回值
function *show(){ console.log('a') let a=yield; console.log('b') console.log(a) } let gen=show(); gen.next(12) gen.next(5) //a //b //5
第一次执行next的时候执行黄色框代码
第二次执行红色框的代码
传参的时候经过yield来传参的时候,第一个next是无效的,
若是想给第一个过程传参须要使用传统方法,在使用函数时传参
function *show(num1,num2){ console.log(`${num1},${num2}`) console.log('a') let a=yield; console.log('b') console.log(a) } let gen=show(11,12); gen.next(12);//无法给yield传参 gen.next(5) //11,12 //a //b //5
function *show(){ console.log('a') let a=yield 12; console.log('b') } let gen=show(11,12); let res1=gen.next(); console.log(res1) let res2=gen.next() console.log(res2) //a //{value: 12, done: false} //b //{value: undefined, done: true}
value是yield 返回的参数
done代码函数是否走完
为何第二次执行完value是空
由于第二次next是执行的最后一道程序,最后一道程序就没有yield 了,若是想返回东西须要使用return
function *show(){ console.log('a') let a=yield 12; console.log('b') return 111; } let gen=show(11,12); let res1=gen.next(); console.log(res1) let res2=gen.next() console.log(res2) //a //{value: 12, done: false} //b //{value: 111, done: true}
yield 究竟是个啥
这种Generator函数适用多个异步请求之间有逻辑分析的状况,好比有一个需求,先请求用户数据,根据用户数据的类型判断用户是普通用户仍是VIP用户,而后再根据判断结果请求普通商品数据或者VIP商品数据
// 借助runner脚本,runner脚本规定Generator函数执行完一个next以后自动执行下一个next runner(function() * (){ let userData = yield $.ajax(...) // 请求用户数据 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 请求vip商品数据 } else { let goods = yield $.ajax(...) // 请求普通商品数据 } })
第一次yield ajax实际上是Promise对象,将Promise对象yield 出去。
yield 给了runner对象
将数据请求完成给data1
这个函数暂停了
使用Generator函数使得代码看起来更像同步代码,其实使用Promise一样能够实现这种效果,只不过得须要在then()函数中嵌套请求。
异步请求的几种方式
$.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/1', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功') }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, }) }, error:function(error){ console.log('失败') }, })
function createPromise(url){ return new Promise(function(resolve,reject){ //异步代码块 //resolve--成功了 //reject--失败了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } Promise.all([ createPromise('./aa'), createPromise('./bb') ]) .then((res)=>{ let [arr1,arr2]=res },(err)=>{ console.log(err) })
runner(function() * (){ let userData = yield $.ajax(...) // 请求用户数据 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 请求vip商品数据 } else { let goods = yield $.ajax(...) // 请求普通商品数据 } })
Promise和Generator相比,Generator并无特别的省事
Promise也有它不适用的地方。我若是是写死要请求接口。那么Promise和Generator确实没太大区别,
Generator他的优势在于适合参杂一些逻辑
比方说在请求一个接口拿到用户信息,根据信息判断他该去请求哪些不一样的接口
感受比普通嵌套还麻烦
带逻辑-Generator
// 借助runner脚本,runner脚本规定Generator函数执行完一个next以后自动执行下一个next runner(function() * (){ let userData = yield $.ajax(...) // 请求用户数据 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 请求vip商品数据 } else { let goods = yield $.ajax(...) // 请求普通商品数据 } })
Promise适合一次请求一堆场景
Generator适合逻辑性请求处理
KOA是nodejs的框架
async其实就是对Generator的封装,只不过async能够自动执行next()。
async function read () { let data1= await new Promise(resolve => { resolve('100') }) let data2 = await 200 return 300 }
async默认返回一个Promise,若是return不是一个Promise对象,就会被转为当即resolve的Promise,能够在then函数中获取返回值。
async必须等到里面全部的await执行完,async才开始return,返回的Promise状态才改变。除非遇到return和错误。
async function fn () { await 100 await 200 return 300 } fn().then(res => { console.log9(res) // 300 })
await也是默认返回Promise对象,若是await后面不是一个Promise对象,就会转为当即resolve的Promise
若是一个await后面的Promise若是为reject,那么整个async都会中断执行,后面的awiat都不会执行,而且抛出错误,能够在async的catch中捕获错误
async function f() { await Promise.reject('error'); await Promise.resolve('hello world'); // 不会执行 } f().then(res =>{ }).catch(err=>{ console.log(err) // error })
若是但愿一个await失败,后面的继续执行,可使用try...catch或者在await后面的Promise跟一个catch方法:
// try...catch async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world // catch async function f() { await Promise.reject('出错了') .catch(e => console.log(e)); // 出错了 return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world