前端相关JavaScript < ES5、ES6、ES7、… >JS

ES6常用知识学习札记

2019-03-14  本文已影响200人  深沉的简单

转载请注明出处

原文连接 http://blog.huanghanlian.com/article/5c7aa6c7bf3acc0864870f9d

es6 是什么

首先弄明白ECMA和js的关系。ECMA是标准,Javascript是ECMA的实现。因为js也是一种语言,但凡语言都有一套标准,而ECMA就是javascript的标准。 在2015年正式发布了ECMAscript6.0,简称ES6,又称为ECMAscript2015。

  1. 历史
    1. ECMAScript和Javascript
      • ECMA是标准,JS是实现
        • 类似于HTML5是标准,IE10,Chrome,FF都是实现
        • 换句话说,将来也能有其他XXXScript来实现ECMA
      • ECMAScript简称ECMA或ES
      • 目前版本
        • 低级浏览器主要支持ES 3.1
        • 高级浏览器正在从ES 5过度ES 6
  2. 历史版本
时间 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

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,在老的版本中不用。惹不起咋躲得起。

编译,转换

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

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

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

变量-let和const

回顾ES5是怎么生明变量的,有什么样的缺点

var的缺点

  1. 可以重复声明
  2. 无法限制修改
  3. 没有块级作用域

可以重复声明

最大的问题

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中有了两种新的定义变量的方式

letconst

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

函数-参数

  1. 参数扩展/数组展开
  2. 默认参数

参数扩展

  1. 收集剩余参数

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();
  1. 展开数组

展开后的效果,跟直接把数组内容写在这一样

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
  1. 左右两边结构必须一样
  2. 右边必须是个合法的数据
  3. 声明和赋值必须一句话完成,不能把声明与赋值分开
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

  1. 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);//["不及格", "不及格", "及格", "及格", "不及格", "及格"]
  1. 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
  1. 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]
  1. forEach 循环(迭代)

数组每个元素都执行一次回调函数。

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

注意: forEach() 对于空数组是不会执行回调函数的。

字符串

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

多了两个新方法

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('其他')
}

//网站地图

字符串模板

模板字符串有两个能力

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

平常写字符串有两种写法,

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();//黄继鹏

这样写的缺点

  1. 类和构造函数不分,
  2. 类散开了,先声明一个构造函数,然后对函数原型添加方法

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

(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)类的实例

//定义类
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 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属性,因此同时存在两条继承链。

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介绍:

  1. 组件化
    在react里一个组件就是一个class,
  2. 依赖于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>
image.png

组件套组件方法例子

<!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可以传参数,也可以有返回值

  1. 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
  1. 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()函数中嵌套请求。

异步请求的几种方式

  1. 回调写法
$.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('失败')
    },
})
  1. 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)
})
  1. 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
上一篇下一篇

猜你喜欢

热点阅读