乐趣区

ES6常用知识学习札记

转载请注明出处
原文连接 http://blog.huanghanlian.com/article/5c7aa6c7bf3acc0864870f9d
es6 是什么
首先弄明白 ECMA 和 js 的关系。ECMA 是标准,Javascript 是 ECMA 的实现。因为 js 也是一种语言,但凡语言都有一套标准,而 ECMA 就是 javascript 的标准。在 2015 年正式发布了 ECMAscript6.0,简称 ES6,又称为 ECMAscript2015。

历史

ECMAScript 和 Javascript

ECMA 是标准,JS 是实现

类似于 HTML5 是标准,IE10,Chrome,FF 都是实现
换句话说,将来也能有其他 XXXScript 来实现 ECMA

ECMAScript 简称 ECMA 或 ES

目前版本

低级浏览器主要支持 ES 3.1
高级浏览器正在从 ES 5 过度 ES 6

历史版本

时间
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<br/> 因为吵得太厉害,所以 ES3.1 代号为 Harmony(和谐)

2009.12
ES 5.0

ES5 正式发布 <br/> 同时公布了 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 预计发布正式版 <br/>JavaScript.next 开始指向 ES 7.0

ES6 兼容性和新特性
es5 兼容性
http://kangax.github.io/compat-table/es5/

es6 兼容性
http://kangax.github.io/compat-table/es6/

ES6(ES2015)– IE10+,Chrome,FireFox,移动端,NodeJS。这些环境基本上都是认得,都能兼容
但是有需求兼容 ie 怎么办
有两种办法
比方说在移动端或者是混合开发当中,多去用用 ES6,在老的版本中不用。惹不起咋躲得起。编译,转换

在线转换

简单来说就是写好了 ES6 了然后引用一个 js 库进来。我什么也不用做了。它替我去做了各种各样的事情
缺点,用户每次打开页面都要重新转换一遍,性能体验不是很好。

提前编译

ES6 的到底有什么样的东西?

变量 (对原有的变量做了修改)
函数 (对原有的函数也做了修改)
数组(对数组做了一些改进)
字符串 (改进)
面向对象
Promise(串行化的异步请求方式)
yield && generator(generator 是专门把同步操作拆成异步操作,generator 是对 Promise 的一个封装)
模块化

变量 -let 和 const
回顾 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

不能重复声明,let 是变量,可以修改,块级作用域
块级作用域
可修改 let 变量的值

const

不可重复声明,const 常量,不能修改,块级作用域
块级作用域
不可修改 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 做了一些修整,代码看起来更显得简洁

如果只有一个参数,圆括号 ”()” 可以省略
函数体如果只有一句 return 语句,花括号也可以省略

// 定义一个箭头函数
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
map 映射
通过指定函数处理数组的每个元素,并返回处理后的数组。一个对一个
[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);//[“ 不及格 ”, “ 不及格 ”, “ 及格 ”, “ 及格 ”, “ 不及格 ”, “ 及格 ”]
reduce 汇总
将数组元素计算为一个值(从左到右)。一堆出一个
算个总数
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() 不会对空数组进行检测。
注意: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() 方法用于调用数组的每个元素,并将元素传递给回调函数。
注意: forEach() 对于空数组是不会执行回调函数的。
字符串

多了两个新方法
字符串模板

多了两个新方法
startsWith() 表示参数字符串是否在原字符串的头部,返回布尔值
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(‘ 其他 ’)
}
endsWith() 表示参数字符串是否在原字符串的尾部,返回布尔值
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(‘ 其他 ’)
}

// 网站地图
includes() 表示是否在原字符串找到了参数字符串,返回布尔值
字符串模板
模板字符串有两个能力

能直接把变量塞到字符串中去。
可以折行

平常写字符串有两种写法,
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();// 黄继鹏
(1)constructor

es6 中 class 类专用的构造器,相当于之前定义的构造函数,每个类都必须有 constructor,如果没有则自动添加一个空的 constructor 构造器。
创建实例的时候自动执行 constructor 函数
constructor 中的 this 指向实例,并且默认返回 this(实例)

(2)class 类的 prototype
其实 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() {},
};
(3) 类的实例

类的实例只能通过 new 来创建
除了静态方法,定义在类上的所有的方法都会被实例继承
除非定义在类的 this 对象上才是实例属性,否则都是定义在类的原型(prototype)上

// 定义类
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
(4) 静态方法
如果在类中定义一个方法的前面加上 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
(5) 静态属性

class 的静态属性指的是 Class 本身的属性,目前只能通过 Class.propName 定义静态属性
静态属性可以被子类继承,不会被实例继承

class User{}
User.name = ‘foo’ // 为 class 定义一个静态属性

class VipUser extends User{}
console.log(VipUser.name) // foo

var foo = new User()
console.log(foo.name) // undefined
(6) 私有属性和私有方法
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 的实例
(1)super
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()
(2) 类的 prototype 和 proto 属性
在 es5 中每一个对象都有 proto 属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。

子类的 proto 属性,表示构造函数的继承,总是指向父类。
子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。

class User {
}

class VipUser extends User {
}

VipUser.proto === User // true
VipUser.prototype.proto === User.prototype // true
(3) 实例的 proto 属性
子类实例的 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 介绍:
组件化
在 react 里一个组件就是一个 class,
依赖于 JSX
jsx==babel==browser.js

<!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 的中文含义是承诺
了解 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 可以理解为生成器,和普通函数没多大区别,普通函数是只要开始执行,就一直执行到底,而 Generator 函数是中间可以停,搭配使用 next 函数继续执行。
生动的比喻
普通函数好比坐飞机,飞机起飞,不到目的地中途是不会降落的
Generator 好比于出租车。可以随叫随停。停了再走,走了再停
(1) 定义一个 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
generator-yield
yield 和 next
yield 代表暂时暂停执行,next 代表继续执行。
yield 和 next 可以传参数,也可以有返回值
yield 可以传参
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
yield 返回
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- 实例:runner
这种 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(‘ 失败 ’)
},
})
Promise 写法
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 写法
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 适合逻辑性请求处理
generator- 实例:KOA
KOA 是 nodejs 的框架

async await
async 其实就是对 Generator 的封装,只不过 async 可以自动执行 next()。
async function read () {
let data1= await new Promise(resolve => {
resolve(‘100’)
})
let data2 = await 200

return 300
}
async 返回值
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
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

退出移动版