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++) { %>
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;
}
}
展开操作符 ..
.
es6 中asign 合并两个对象,浅克隆
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>