ECMA6

时间:2021-09-12 09:24:47
let关键字

用来替代var 的关键字,不能重复定义一个变量

举例:
for(var i=0; i<5; i++){
     setTimeout(function(){
          alert(i); 
     },1000);
}
你认为弹出的结果是多少?
结果: 5,5,5,5,5

是不是跟你认为的0,1,2,3,4不太一样???
原因就在于,var i=0,定义的不是局部变量,而是全局变量,这里不会形成闭包。

再举例:
for(var i=0; i<5; i++){
     for(var i=0; i<5; i++){
          console.log(i);
     }  
}
你认为打印的结果是多少?
结果: 1 2 3 4 5

难到不是打印25次么?
原因还是因为,像for循环的大括号,是不会形成作用域的

那么如何在一个语句块中定义局部变量呢?
那就是使用let,不过目前大多数的ES6只允许在严格模式下使用

"use strict"
for(let i=0; i<3; i++){
     setTimeout(function(){ alert(i) },1000);
}

我们尝试一下if语句的大括号
"use strict";
let a = 10;
if(window) {
     let a = 100;
     console.log(a);
}
console.log(a);
结果: 100  10

从标准的ES6开始,我们有了块级作用域

例如:
function m(){  console.log("111111111") }
if( flag > 5) {
     function m(){
           console.log("2222222222")
     }
}
m();

在ES5中,函数m会在第二次定义时被覆盖。 结果: 222222222222
在ES6中,则会出现 111111111111的结果。

const

我们终于可以定义常量了!
const GD_TYPE_NORMAL = 1;

=>箭头函数 (应用场景:回调函数)

这个我个人不推荐使用,待会再说原因,先讲优点!

先看区别吧,原来的写法
var test = function(x){
     return x+2;
}
使用箭头函数:
var test = x=>x+2; //可以理解为x经过一系列的变化返回x+2,即:return x+2

若不想有返回值 var test = x =>{ x+2 }

数组排序 [1,43,54,23].sort((a,b)=>a-b);

看起很简单吧? 省略了function、return关键字和大括号。  使用方法跟以前一样没区别
test(5);  结果: 7
var test = (x+y)=>x+y;

还有好处就是,自动绑定this,或者说this指向不发生改变
var obj = {
     left : 200,
     move : function(){
          setTimeout(function(){
               //this.left = 100;  
               //以前这里不能写this
          },1000);
     }
}
使用了箭头函数:
var obj = {
     left : 200,
     move : function(){
          setTimeout( ()=>{
              this.left = 100;  
          },1000);
     }
}

当然也有一些缺陷
第一:
箭头函数是不能new的,它的设计初衷就跟构造函数不太一样
第二:
箭头函数如果要返回一个JSON对象,必须用小括号包起来
var test = ()=>({id:3, val=20})

箭头函数现在非常流行,但我个人并不觉得它有那么美好。
主要是因为,这样的设计对代码的可读性伤害太大了

引用国际著名OO专家、敏捷开发创始人 马丁.福勒的一句名言:
任何一个傻瓜都能写出计算器可以理解的代码。惟有写出人类容易理解的代码,才是优秀的程序员。

……我们读代码的时间和写代码的时间比率是10:1。这意味着我们大部分时间都在阅读老代码,
以便于之后新代码的编写。因为读代码占得比重太大了,因此我们希望在阅读代码的时候能够更
加轻松,即便在编写代码的时候需要费点劲。
这一段好像也是他老人家说的,我觉得很有道理。

省略掉一个function单词,并不能给开发效率提高多少,但牺牲的却是最基本的代码可读性
除了数学运算,我们几乎从来不用符号表示一些复杂的含义。
甚至以前有人质疑过JQ关于each方法的设计思想,认为它屏蔽了程序最基本的逻辑,那就是循环
代码的可读性收到了严重伤害。不过我们仔细看看forEach、map、filter这些函数,
尽管它们屏蔽了for循环,但分析这些单词的含义:
each 每个
map 映射(一一对应)
filter 过滤
它们其实全部都隐含 的表达了遍历的意思。

回过头来我们再看,从function到=>,这东西连个象形符号都算不上
这就是为什么数学有那么多的符号可以用来交流,但我们却从不把它称为语言

再举个例子,你看下面这段代码:
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

给你3分钟,能看懂吗?
它为什么这么复杂?实际上就是因为它全部都使用了非象形符号表示的
因此正则表达式才会成为最难交流以及阅读的编程语言

但是!我们必须承认,它确实给我们的验证工作提升了百倍以上的效率
权衡利弊,我有理由喜欢它

但是这个箭头函数,在多数情况下,我不推荐使用
就像你的代码里不能满屏幕充斥着  
三目运算符   和  Math.max(xx, Math.min(xx, obj) )这样的东西
这样真的好吗?

在写回调的时候,偶尔用用箭头函数,还是不错的
[2,3,9,14,8,29,93,30].sort( (a,b) => a-b );

个人观点,仅供参考。


Destructuring 解构

以前我们定义多个变量,可以这样写:
var x=10,y=20,z=30;

现在我们可以这样写:
let [x,y,z] = [10,20,30];
let [x,[a,b],y] = [10,[15,18],20];
let {id,name,age} = {id:1,name:"jide",age:99};


这并不是在定义数组,而是通过等号两边的结构匹配,进行赋值。

当然,如果你写的不够规范:

let [x,y] = [10,20,30]; //结果x=10,y=20
let [x,[a,b],y] = [10,[5],20];  //结果x=10,a=5,y=20,b=undefined
浏览器并不会报错,依然赋值成功。

甚至可以使用对象来赋值
var {id,name,age} = {id:43,name:'yt',age:30}

解构赋值的用途:
1 交换变量的值   
[a,b] = [b,a];  //排序可能会常用到

2 函数返回多个值,这个功能比较有颠覆性
var [r1,r2,r3] = exec();
function exec(){
     return ["结果a","结果b","结果c" ];
}

3 函数的参数定义方式, 不用再考虑顺序
function exec({time, speed, pos}){
}
执行函数:
exec({
     pos: {x:20,y:55},
     speed: 50,  
     time: 2000
})

默认值也可以顺便带上:
function exec({ time=2000, speed=50, pos}){
}

3 获取数组的指定元素(第一个和最后一个)
var {0:head,arr.length-1:last} = arr; 


字符串的一些方法


字符串模板, 使用反引号`表示,使用${变量|函数}嵌入代码


var username = "flq";
var introduce = function(){ +
return ''haha";
}
$(temp).html(
     `你好,我的名字叫${username}
      接下来是我的自我介绍:${introduce()}`//introduce必须有返回值,否则为undefined
);

使用`反引号包裹的字符串,可以当场模板来使用。
利用${ username } 输出变量
利用${ introduce() } 执行函数

经过测试,字符串模板默认会被转义,因此写入标签的话,会被原样输出



写一个自己的模板引擎

var template = `<ul>
                <% for(var
i=0; i < data.names.length; i++) { %>
                   <li><%= data.names[i] %></li>
                <% } %>
               </ul>       
               `;

test(template, {names:["张三","李四","tony stark","hulk"]})

function test(temp, obj){
    //正则处理字符串
    temp = temp.replace(/<%=(.+?)%>/g,"`) \n print($1) \n print(`");
    temp = temp.replace(/<%(.+?)%>/g,"`) \n $1 \n print(`");
    temp = "print(`"+temp+"`)";

    //经过正则的处理,我们希望模板字符串变成如下形式:
    /******************************************* 
    print(`<ul>`)
    for(var i=0; i<data.names.length; i++){
        print(`<li>`) 
        print(data.names[i]) 
        print(`</li>`)
    }
    print(`</ul>`)
    *********************************************/

    //准备一段代码,动态生成一个函数,把刚才准备好的temp代码嵌入当中
    var funcStr = `(function(data){
        var strhtml = "";
        function print(str){
            strhtml += str;
        }
        ${temp} //把temp字符串放入该函数中,形成一个完整的函数
        return strhtml;
    })`;

    /*************这段代码生成后的样子******************
    (function(data){
        var strhtml = "";
        function print(str){
             strhtml += str;  
        } 
        print(`<ul>`)
   for(var i=0; i<data.names.length; i++){
        print(`<li>`)
        print(data.names[i])
        print(`</li>`)
   }
        print(`</ul>`)
        return strhtml; 
    })
    *********************************************/

    //使用eval执行代码,生成并返回这个函数
    var func = eval(funcStr);

    //调用这个函数,得到最终的字符串模板
    var res = func(obj);

    return res;
}


Math的一些方法

请自行查阅文档

数组的一些方法

将伪数组转为数组:
var list = Array.from(document.getElementsByTagName("li"));   

复制指定内容覆盖指定内容(指定数组的下标6、7替换下标2及以后
var arr = [1,2,3,4,5,6,7,8,9,0];
arr.copyWithin(2,6,8);
//[1,2,7,8,5,6,7,8,9,0] 

//find跟过filter差不多,不过在找到符合条件的元素后,返回元素并停止遍历
[1, 5, 10, 15].find(function(value, index, arr) {
     return value > 9;
})
// 10

//跟find差不多,不过在找到符合条件的元素后,返回该元素的下标
[1, 5, 10, 15].findIndex(function(value, index, arr) {
     return value > 9;
})
// 2


Object.assign

合并对象

Object.assign({a:1},{b:2},{b:4,c:3});
//{a:1,b:4,c:3}


当然合并过程中做的是浅拷贝
var a1 = {
    name: "aaaa",
    firend: {
        name: "cccc"
    }
}
var a2 = {
    age : 30
}
var b = Object.assign(a1,a2)
a1.friend == b.friend

对于原型属性忽略
var a = {
    name: "aaaa"
}
var b = {
    age : 20
}
b.__proto__.title = "ma";
var c = Object.assign(a,b);


对于不可枚举属性忽略
var a = {
    name: "aaaa"
}
var b = {
    age : 20
}
Object.defineProperty(b, "age", { //ECMA5
    enmuable : false
})
对于引用类型,只拷贝引用

第七种数据类型Symbol

var s1 = Symbol();
var s2 = Symbol();
var s3 = Symbol("abc");
var s4 = Symbol("abc")

s1不等于s2    s3不等于s4

Symbol函数会生成一个唯一的值
可以理解为Symbol类型跟字符串是接近的
但每次生成唯一的值,也就是每次都不相等,至于它等于多少,并不重要
这对于一些字典变量,比较有用

const TYPE = {
     SMALL: Symbol(),
     MIDDLE: Symbol() ,
     LARGE: Symbol(),
     COM_SMALL: Symbol(),
     COM_MIDDLE: Symbol()
}
//以前我们可能会把SMALL、MIDDLE、LARGE赋值为数字或字符串
//还要确保它们的值不能发生重复,但现在不用担心了

function Create(type){
     switch(type){
          case TYPE.SMALL : {
              .....
              break; 
          }
          case TYPE.MIDDLE : {
              .....
              break;
          }
          case TYPE.LARGE : {
              .....
              break;
          }
     }
}

var s = Create(TYPE.MIDDLE);


Set和Map集合

想当初设计JS的时候,由于有SUN公司人员的参与
再加上当时如日中天的JAVA及其优秀的设计,才使得JS语法及内存设计跟JAVA会如此的接近。

但JAVA很多优秀的内容,JS不知道为了什么目的并没有引入,例如Set和Map集合

Set集合,本质上就是对数组的一种包装(与map相同,但其key值与value值相等)
例如:

let imgs = new Set();
imgs.add(1);
imgs.add(1);
imgs.add(5);
imgs.add("5");
imgs.add(new String("abc"));
imgs.add(new String("abc"));

打印的结果:
1  5  '5'  'abc'  'abc'

Set集合是默认去重复的,但前提是两个添加的元素严格相等
所以5和"5"不相等,两个new出来的字符串不相等

如何删除元素 set.delete(key)
imgs.delete(5);

imgs.delete('5');

关于遍历的方法
由于Set集合本质上还是一个map,因此会有以下几种奇怪的遍历方法
var imgs = new Set(['a','b','c']);
//根据KEY遍历
for(let item of imgs.keys()){
     console.log(item);
}
//a
//b
//c

//根据VALUE遍历
for(let item of imgs.values()){
     console.log(item);
}
//a
//b
//c

//根据KEY-VALUE遍历
for(let item of imgs.entries()){
     console.log(item);
}
//['a','a']
//['b','b']
//['c','c']

//普通for...of循环(for...of跟for-in的区别很明显,就是直接取值,而不再取下标了)
for(let item of imgs){
     console.log(item);
}
//a
//b
//c

SET集合没有提供下标方式的访问,因此只能使用for来遍历。

// 下面展示了一种极为精巧利用set集合对数组去重的方法
var newarr = [...new Set(array)];
=============================================================================

Map集合,即映射(底层也是数组,但其根据key值进行哈希运算后进行存储,查询效率远大于数组)
let map = new Map();
map.set("S230", "张三");
map.set("S231", "李四");
map.set("S232", "王五");

获取某一个元素
map.get("s232");
//王五

//循环遍历,配合解构赋值
for(let [key,value] of map){
     console.log(key,value);
}

map.has(key);  //返回布尔值,判断是否含有键key
map.delete(key);  //删除
map.size    //长度
map.clear()   //清空

Promise规范

新推出的Promise函数,是一个构造函数,它实际上是对回调函数的一种封装
对异步编程的一种改进,当然,如果只是写法上的改进,那就意义不大了。
下面我们看看它能解决哪些以前难以解决的问题:

问题1: 当有两个或以上的ajax请求需要按顺序执行,该如何编写?
//以下是伪代码
ajax1({
     url: "xxxxx",
     success: function(data1){
          //在这里,当ajax1完成后,才能开启ajax2 
          ajax2({
               url: "xxxxx",
               success: function(data2){
                   //第二次完成的回调,可能还有第三个\第四个无止境的嵌套....
               }
          }); 
     },
     error: function(msg){
         console.log("请求出现错误:",msg); 
     }
});

而使用promise的写法如下:
new Promise(function(resolve, reject){
     ajax1({
          url: "xxxxx",
          success: function(data){
               resolve();
          },
          error: function(msg){
               reject();
          }
     });
}).then(function(){
     ajax2({
          url: "xxxx",
          success: function(data){
          } 
     });
}).catch(function(){
     console.log("请求出现错误:",msg);
});

也就是代码被改进成了new Promise(ajax1).then(ajax2).catch()
使用了链式调用代替了嵌套结构。

resolve() 和 reject() 则意味着成功回调和失败回调的执行。

如果有三个或更多的ajax请求呢?
new Promise(function(resolve, reject){
     ajax1({
          url: "xxxxx",
          success: function(data){
               resolve();
               //use data.......
          },
          error: function(msg){
               reject();
          }
     });
}).then(function(){
return new Promise(function(resolve, reject){
     ajax2({
          url: "xxxx",
          success: function(data){
               resolve();
               //use data.......
          },
          error: function(msg){
               reject();
          }
     })
})
}).then(function(){
     ajax3({
          url: "xxxx",
          success: function(data){
                 //use data.......
          },
          error: function(msg){
                //print msg
          }
     })
});
为了简化代码,我这里将catch方法省略了
代码结构大概是这样的: 
new Promise(ajax1).then(function(){ return new Promise(ajax2) }).then(ajax3);

如果有第四个请求要顺序执行,则大概写成这样:
new Promise(ajax1)
.then(function(){
     return new Promise(ajax2)
})
.then(function(){
     return new Promise(ajax3)
)
.then(ajax4);

从这里仔细观察,可以看出,只有被Promise封装过了,回调才能保证顺序。
也就是Promise(意为承诺)设计的初衷

但前一个方法必须在它最后,执行resolve(),后一个方法才可以开始。
如果执行了reject(),则进入catch()方法。 

其实这里不能单纯的理解为 resolve就是success,reject就是error
现就职于阿里的大名鼎鼎的阮一峰老师,喜欢管它叫状态机,这是非常恰当的叫法。

只有理解了它的设计思路,才能明白为什么叫状态机。

//我们把代码写的再实际一些
new Promise(function(resolve, reject){     //p1
          ajax1({
                   .........
                   resovle() 
          });
}).then(function(){
          return new Promise(resolve, reject ){  //p2
          ajax2({
                   .........
                   resovle() 
          });
          }
}).then(function(){
          return new Promise(resolve, reject ){   //p3
          ajax3({
                   .........
                   resovle() 
          });
          }
})

如果,我们把每一个promise对象都看做一个状态机的话,它实际上只有三种状态
进行中已完成已失败, 它分别代表了所承诺的回调函数的执行状态。

而resolve方法,只是把这个状态机从Pending(进行中) ,改成了Resolved(已完成)

由于p2现在依赖了p1,因此,p2会观察p1的状态,直到p1变为Resolved
p2才会开始, 但这时then方法返回的不再是p1,而是p2,因此p3会依赖p2的状态


问题2: 如果有三个ajax请求,调用顺序无关,但必须保证三个都成功,才能开始第四个?

Promise.all(function(){   // p123
     return [p1,p2,p3];
}).then(function(){
     return p4;
});

p4依赖p123的状态,那么理解了状态机,promise是不是如此简单呢?

问题3: 如果有三个ajax请求,只要任意一个成功,则开始第四个?

Promise.race(function(){   //p123
     return [p1,p2,p3];
}).then(function(){
     return p4;
});

Class保留字终于成了关键字

终于,我们在有生之年等到class出现了
这下,JS看起来更像一个标准的面相对象的语言了

以前编写一个构造函数(类)
function Pad(color){
     this.color = color;
}

现在的写法跟Java更接近了
class Iphone{
     constructor(color, size){
            this.color = color; 
            this.size = size;
     }
     playgame(){
            //.............
     }
     toString(){
           return `这台手机的颜色是${this.color} 屏幕大小是${this.size}`;
     }
}

我们定义了一个类,名字叫Iphone
通过类生成一个实例:
var iphone = new Iphone("白色", 5);

其中constructor被称之为构造方法,在我们new 一个对象的时候,自动被调用

不过本质上,JS依然使用了原型来实现,也就是说,这不过是一个新的写法而已
跟以前的构造函数没有区别。

要注意的是,使用了class来定义类,必须先定义再使用
以前这样写没有问题:
new Person();
function Person(){
}
现在这样写报错:
new Person();
class Person{
       constructor(){

       }
}

甚至还可以定义一次性的类
let person = new class{
        constructor(){
        }
}();
这个类被定义出来,只能实例化一次,跟JAVA的匿名内部类很像


关于继承
class Son extends Father{
     constructor(age){
            super(); //这相当于调用了父类的构造方法,类似于传统写法Father.call(this)
            //但是这个方法是必须放在第一行先调用
            this.age = age;
     }
}




展开操作符 ..

.ECMA6

es6 中asign 合并两个对象,浅克隆

ECMA6

Promise 实例

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
<script type="text/javascript">
function ajaxA(success){
console.log("ajaxA start");
setTimeout(function(){
console.log("ajaxA finished");
success();
},2000);
}
function ajaxB(success){
console.log("ajaxB start");
setTimeout(function(){
console.log("ajaxB finished");
success();
},2000);
}
function ajaxC(success){
console.log("ajaxC start");
setTimeout(function(){
console.log("ajaxC finished");
success();
},2000);
}
function ajaxD(){
console.log("ajaxD start");
setTimeout(function(){
console.log("ajaxD finished");
},2000);
}
new Promise(ajaxA).then(function(){
return new Promise(ajaxB);
}).then(function(){
return new Promise(ajaxC);
}).then(ajaxD);
</script>
</html>



















相关文章