欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ES6基础

程序员文章站 2022-05-03 17:45:30
...

ECMAScript(简称ECMA或ES)和JavaScript(简称JS)的关系是什么呢?

ES是标准,JS是实现。

变量声明

ES5中var有什么缺陷呢?

  • var可以重复声明变量,带来的问题是会覆盖掉别人已经声明的变量。
  • 无法限制var声明变量的修改

变量作用域

JS的作用域只有全局作用域global scope和函数作用域function scope两种,并没有块级作用域。所以,在JS中的代码块中使用var声明的变量的作用域都是隶属于全局作用域的。

var price = 100;
var count = 5;
if(count > 3){
    var discount = price * 0.5;
    console.log(discount);//50
}
console.log(discount);//50

函数内声明的局部变量的作用域只在函数内有效

function fn(){
    var cnt = 5;
    console.log(cnt);
}
fn();//5
console.log(cnt);//Uncaught ReferenceError: cnt is not defined

ES6中有两种定义变量的方式,分别是constlet关键字。

使用关键字let声明变量

  • let声明的变量是可以被修改的
  • let声明的变量不能被重复声明
let discount = 10;//作用域在全局
let discount = 100;//Uncaught SyntaxError: Identifier 'discount' has already been declared
  • let声明的变量拥有块级作用域block scope
let count = 5;
let discount = 10;//作用域在全局
if(count > 3){
    let discount = 100;//作用域在块级
}
console.log(discount);//10

使用关键字const声明常量

  • const声明的常量是不可以被修改的
const pi = 3.14;
pi = 3.1415;//Uncaught TypeError: Assignment to constant variable.

如果使用const声明的是对象(引用),那么是可以改变对象的成员属性的,不变的是引用指针所指向的地址。

const obj = {id:1, name:"alice"};
obj.id = 2;
console.log(obj);//{id: 2, name: "alice"}

如果不允许改变对象的属性值,可使用Object.freeze(object)方法来冻结。

const obj = {id:1, name:"alpha"};

const o = Object.freeze(obj);
console.log(o);//{id: 1, name: "alpha"}

obj.id = 2;
console.log(obj);//{id: 1, name: "alpha"}

o.id = 3;//Attempt to assign to const or readonly variable
console.log(o);//{id: 1, name: "alpha"}
  • const声明的常量不能重复声明
  • const声明的常量拥有块级作用域

变量与常量

ES6中使用let声明变量的值是允许修改的,使用const声明的常量的值是禁止修改的。

let i = 1;
i = 10;
console.log(i);//10

const PI = 3.14;
PI = 3.14159;//Uncaught TypeError: Assignment to constant variable.

变量重复声明

ES5中使用var声明的变量是可以被重复赋值的

var i = 1;
console.log(i);//1
var i = 10;
console.log(i);//10

ES6中使用let声明的变量禁止重复声明

let i = 1;
let i = 10;//Uncaught SyntaxError: Identifier 'i' has already been declared

ES6中使用const声明的常量禁止重复声明

const PI = 3.14;
const PI = 3.14;//es6.js:2 Uncaught SyntaxError: Identifier 'PI' has already been declared

块级作用域

ES5中没有块级作用域,只有函数作用域。

使用ES5中的var声明变量,由于缺乏块级作用域,带来了问题。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js</title>
</head>
<body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <script>
        window.onload = function(){
            var btns = document.getElementsByTagName("button");
            for(var i=0; i<btns.length; i++){
                btns[i].onclick = function(){
                    alert(i);
                }
            }
        }
    </script>
</body>
</html>

问题:每次都会输出3

使用函数闭包的方式解决块级作用域问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js</title>
</head>
<body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <script>
        window.onload = function(){
            var btns = document.getElementsByTagName("button");
            for(var i=0; i<btns.length; i++){
                (function(i){
                    btns[i].onclick = function(){
                        alert(i);
                    }
                })(i);
            }
        }
    </script>
</body>
</html>

使用ES6中的let声明的变量具有块级作用域

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>es6</title>
</head>
<body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <script>
        window.onload = function(){
            let btns = document.getElementsByTagName("button");
            for(let i=0; i<btns.length; i++){
                btns[i].onclick = function(){
                    alert(i);
                }
            }
        }
    </script>
</body>
</html>

IIFE 立即执行函数表达式

使用letconst替代立即执行函数表达式(IIFE, Immediately-Invoked Function Expression),立即执行函数表达式用于生成一个私有变量。

由于ES5中使用var声明的变量,很容易就会污染全局作用域。例如JS的window对象具有一个name的属性,如果直接使用var定义一个全局变量name则会直接覆盖window对象的name属性。

console.log(name, window.name);
var name = "junchow";
console.log(name, window.name);//junchow junchow

使用立即执行函数表达式可以有效地避免污染全局变量,使变量私有化。

console.log(name, window.name);//"" ""
(function(){
    var name = "junchow";
    console.log(name, window.name);//junchow ""
})();
console.log(name, window.name);//"" ""

使用立即执行函数表达式,虽然使变量私有化了,但并不利用代码的可读性。

const name = "junchow";
console.log(name, window.name);//junchow ""
let name = "junchow";
console.log(name, window.name);//junchow ""
{
    const name = "junchow";
    console.log(name, window.name);//junchow ""
}

怪异的for循环

例如:在for循环中经常会遇到的问题

for(var i=0; i<3; i++){
    console.log(i);// 0 1 2
}
console.log(i);//3
for(var i=0; i<3; i++){
    console.log(i);//0 1 2
    setTimeout(function(){
        console.log(i);// 3 3 3
    },1000);
}
console.log(i);//3

使用ES6的let声明循环变量

for(let i=0; i<3; i++){
    console.log(i);//0 1 2
    setTimeout(function(){
        console.log(i);// 0 1 2
    },1000);
}
console.log(i);//Uncaught ReferenceError: i is not defined

临时性死区

ES6中,在代码块内使用letconst声明变量前,变量都是不可用的。在语法上,称之为临时性死区(TDZ, Temporal Dead Zone)。

使用var声明的变量时存在着变量的提升

console.log(color);//undefined
var color = "red";

为什么是undefined呢?实际上上述代码相当于:

var color;
console.log(color);//undefined
color = "red";

JS中函数以及变量的声明都将被提升到函数的最顶部,JS中变量可以在使用后声明,也就是变量可以先使用再声明。

console.log(color);//Uncaught ReferenceError: Cannot access 'color' before initialization
let color = "red";

变量赋值

解构赋值

  • 表达式左右两边结构必须一模一样
  • 表达式右侧必须是值
  • 声明和赋值不能分开

数组解构赋值

//ES5解构赋值
var arr = [1,2,3];
var x = arr[0];
var y = arr[1];
var z = arr[2];
console.log(x, y, z);//1 2 3
//ES6数组解构赋值
let [x, y, z] = [1, 3, 5];
console.log(x, y, z);//1 3 5

JSON解构赋值

let {id, name} = {id:1, name:"alpha"};
console.log(id, name);//1 "alpha"

字符串

  • ES6新增方法startsWithendsWith
  • ES6新增字符串模板

String.startsWith

startsWith表示参数字符串是否在原字符串的头部,返回布尔值。

string.startsWith(substring[, searchPos]):boolean

例如:验证URL地址的合法性

let url = "http://www.baidu.com";
let bool = url.startsWith("http://");
console.log(bool);//true
  • startsWith第二个参数为起始位置

例如:验证身份证号码

const identify = "51030019800730366x";
//判断是否为四川省
console.log(identify.startsWith("51"));//true
//判断是否为1980年出生的,startsWith第二个参数为从第几个位置开始
console.log(identify.startsWith("1980", 6));//true
  • startsWith对大小写敏感
const str = "jack love mary";
console.log(str.startsWith("jack"));//true
console.log(str.startsWith("Jack"));//false

String.endsWith

endsWith表示参数字符串是否在原字符串的尾部,返回布尔值。

let url = "aaa@qq.com";
let bool = url.endsWith("@hotmail.com");
console.log(bool);//true

String.repeat 重复

  • repeat(count)方法会返回一个新字符串表示将原字符串重复count次后的结果
  • repeat(count)方法的参数count如果是小数则会被向下取整
  • repeat(count)方法的参数count如果是负数或Infinity则会报错
  • repeat(count)方法的参数count如果是NaN则等于0
  • repeat(count)方法的参数count如果是字符串则会先转换为数字
console.log(`${'='.repeat(10)}`);//==========
console.log(`${'='.repeat(10.9)}`);//==========
console.log(`${'='.repeat(0)}`);//
console.log(`${'='.repeat(NaN)}`);//
console.log(`${'='.repeat("10")}`);//==========
console.log(`${'='.repeat(-10)}`);//Uncaught RangeError: Invalid count value
console.log(`${'='.repeat(Infinity)}`);//Uncaught RangeError: Invalid count value

例如:实现字符串右对齐

//右对齐
const pad = (string, length=0)=> `${' '.repeat(Math.max(length - string.length, 0))}${string}`;

console.log(pad("hello", 20));//               hello
console.log(pad("world", 20));//               world

模板字符串

  • 传统字符串拼接无法正常换行
  • 传统字符串拼接无法友好的插入变量
  • 传统字符串拼接无法友好的处理单引号和双引号相互嵌套的问题

ES6模板字符串使用一对反引号来定义一个模板字符串

let obj = {id:1, title:"test"};
console.log(`id = ${id} title = ${title}`);

模板字符串的应用

const User = {
    id:1,
    name:"junchow",
    date:"2019-08-28",
    todos:[
        {todo:"go to store", status:false},
        {todo:"watch movie", status:true},
        {todo:"running", status:true}
    ]
};
//模板字符串可以进行嵌套
const template = `<ul>${User.todos.map(item=>`<li>${item.todo}</li>`).join("")}</ul>`;
console.log(template);

document.querySelector("#app").innerHTML = template;
const User = {
    id:1,
    name:"junchow",
    date:"2019-08-28",
    todos:[
        {todo:"go to store", status:false},
        {todo:"watch movie", status:true},
        {todo:"running", status:true}
    ]
};
//模板字符串可以进行嵌套
const template = `
<ul>
${User.todos.map(item=>`
    <li>
        ${item.todo}
        ${item.status===true?"done":"undo"}
    </li>
`).join("")}
</ul>`;
console.log(template);

document.querySelector("#app").innerHTML = template;

标签模板字符串

function tag(template, ...args){
    const html = args.map(arg=>`<strong>${arg}</strong>`);
    let ret = "";
    template.forEach((item, index) => ret +=`${item} ${html[index]}`);
    return ret;
}

const id = 1;
const name = "junchow";
const sentence = tag`${id} ${name}`;
console.log(sentence);

数组

数组解构Array Destructuring

ES6允许按照一定模式,从数组中提取值,同时定义变量并对其赋值,这也就是解构(Destructureing)。

const result = [1, "success"];

//const [code, message, data] = result;
//console.log(code, message, data);//1 "success" undefined

//const [code, ,data] = result;
//console.log(code, data);//1 undefined

//const [code, ...args] = result;
//console.log(code, args);//1 ["success"]

//const [code=0, message="", data={}] = result;
//console.log(code, message, data);//1 "success" {}

例如:交换变量值

let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y);//20 10

Array.protype.includes() 包含

ES5数组提供Array.indexOf()方法用来查找某个元素在数组中的索引位置,若不存在则返回-1。

indexOf方法判断数组中是否包含元素是存在两点不足之处

  1. 返回-1和元素的索引位置表示是否包含不够语义化
  2. 不能判断是否含有NaN元素
const arr = ["alice", "card", 1, 2, NaN];
console.log("%s", arr.indexOf("alice"));//0
console.log("%s", arr.indexOf(0));//-1
console.log("%s", arr.indexOf(NaN));//-1

ES6提供Array.includes()方法返回布尔值可用于判断数组中是否包含指定元素,对NaN同样有效,其不足之处就是无法进行定位。

  • includes方法第1个参数表示查询的目标字符串
  • includes方法第2个参数表示判断的起始索引位置
  • includes方法第2个参数可以是负数,表示从右倒数第几个,但不会改变搜索方向,搜索方向仍然会从左至右。
const arr = ["alice", "card", 1, 2, NaN, "alice"];
console.log("%s", arr.indexOf("alice"));//0
console.log("%s", arr.indexOf(0));//-1
console.log("%s", arr.indexOf(NaN));//-1

console.log("%s", arr.includes("junchow"));//false
console.log("%s", arr.includes("alice"));//true
console.log("%s", arr.includes("alice", 4));//true
console.log("%s", arr.includes("alice", -5));//true

ES6中新增了四个函数,分别是映射map、汇总reduce、过滤filter、循环forEach

Array.map 映射

// 将数组中所有元素增加2倍
let arr = [11, 23, 43, 90];
let result = arr.map((x)=>x*2);
console.log(result);//[22, 46, 86, 180]
let arr = [89, 23, 31, 78];
let result = arr.map((x)=>{
    if(x>=90){
        return "A"
    }else if(x>=80){
        return "B";
    }else if(x>=60){
        return "C";
    }else{
        return "D";
    }
});
console.log(result);//["B", "D", "D", "C"]

Array.prototype.reduce() 汇总

reduce方法接受一个函数作为累加器并指定初始值(可选),数组中的每个值(从左至右)开始缩减,最终计算为一个值。

array.reduce(callback, [initialValue])

reduce方法的累加器函数callback接受四个参数

function callback(preValue, curValue, index, array)
  • preValue初始值或上一次回调函数的返回值
  • curValue当前元素值
  • index当前索引
  • array调用reduce方法的数组

例如:数组求和与平均数

let arr = [100, 200, 300];

//求和 tmp为中间结果 item为每次迭代的对象 index为索引
let total = arr.reduce((tmp, item)=>{
    //100 200 1
    //300 300 2
    //console.log(tmp, item, index);
    return tmp + item;
});
//console.log(total);//600

例如:求平均值

let arr = [100, 200, 300];
//求平均数
let average = arr.reduce((tmp, item, index, arr)=>{
    if(arr.length === index+1){
        return (tmp+item)/index;
    }else{
        return tmp + item;
    }

});
console.log(average);//300

例如:求阶乘

let arr = [100, 200, 300];
//求阶乘
let result = arr.reduce((preval, curval)=>{
    return preval * curval;
});
console.log(result);//6000000

例如:二维数组转化为一维数组

let arr = [
    [1, 3, 5],
    [2, 4, 6],
    [10, 20, 30]
];
let result = arr.reduce((retval, current, index, arr)=>{
    return retval.concat(current);
});
console.log(result);//[1, 3, 5, 2, 4, 6, 10, 20, 30]

例如:计算数组中元素出现的次数

let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];

let result = arr.reduce((retval, current, index, arr)=>{
   if(current in retval){
       retval[current]++;
   } else{
       retval[current] = 1;
   }
   return retval;
},{});

console.log(result);//{alice: 1, bob: 3, carl: 2}

例如:计算数组中某个元素出现的次数

let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];

let times = (arr, val)=>arr.reduce((retval, current)=>{
    return current===val ? retval+1 : retval+0;
},0);

console.log(times(arr, "bob"));//3

例如:获取数组对象中某个指定字段的值

let arr = [
    {id:11, name:"alpha", status:1},
    {id:20, name:"alice", status:0},
    {id:34, name:"bob", status:1}
];
//获取数组中所有的id
let getField = (arr, field) => arr.reduce((preval, curval)=>{
    preval.push(curval[field]);
    return preval;
}, []);
console.log(getField(arr, "id"));//[11, 20, 34]

例如:数组去重

let arr = ["alice", "bob", "carl", "bob", "carl", "bob"];

let dedupe = (arr)=>arr.sort().reduce((retval, current)=>{
    if(retval.length===0 || retval[retval.length-1]!==current){
        retval.push(current);
    }
    return retval;
},[]);

console.log(dedupe(arr));// ["alice", "bob", "carl"]

例如:求某用户的单科成绩根据权重比例汇总后的权重值

//单科成绩
let arr = [
    {subject:"math", score:89},
    {subject:"chinese", score:89},
    {subject:"english", score:89},
];
//权重比例
let percent = {math:0.5, chinese:0.3, english:0.2};
//求权重值
let result = arr.reduce((preval, curval)=>{
   return preval + curval.score *percent[curval.subject];
}, 0);
console.log(result);//89

例如:对用户非法输入进行过滤

ES6基础
过滤用户非法输入
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>dompurify</title>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/dompurify/1.0.11/purify.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <h1>过滤输入</h1>
                <form action="" class="form" id="form">
                    <div class="form-group">
                        <textarea class="form-control" id="input" rows="7"></textarea>
                    </div>
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary" id="submit">发送</button>
                    </div>
                </form>
                <div class="card card-default" id="card"></div>
            </div>
        </div>
    </div>
    <script>
        //使用dompurify过滤用户输入
        const sanitize = (strings, ...args) => DOMPurify.sanitize(
            strings.reduce( (preval, curval, index) => `${preval}${curval}${args[index]||''}`, "" )
        );
        //获取用户输入并过滤
        const title = "Input";
        const form = document.querySelector("#form");
        const input = document.querySelector("#input");
        const card = document.querySelector("#card");
        form.addEventListener("submit", function(event){
           event.preventDefault();//取消事件的默认动作
           const val = input.value.trim();
           if(val){
               card.innerHTML = sanitize`<div class="card-header">${title}</div><div class="card-body">${val}</div>`;
               input.value = "";
           }
        });
    </script>
</body>
</html>

Array.protype.filter() 过滤器

例如:过滤掉数组中的空字符串、undefinednull和0

let arr = [[], "", '', undefined, "undefined", null, 0, 1];
let result = arr.filter(item=>item);
console.log(result);// [Array(0), "undefined", 1]

例如:获取用户列表中性别为1(男性)的数据

let arr = [
    {id:1, name:"alice", gender:0},
    {id:2, name:"bob", gender:1},
    {id:3, name:"carl", gender:1},
    {id:4, name:"eva", gender:0}
];

let filterField = (arr, field, value)=>arr.filter((item)=>{
    return item[field] === value;
});

console.log(filterField(arr, "gender", 1));

例如:数组去重

let arr = [1, 3, 1, 4, 2, 5, 2, 43, 4, 2, 3];
let result = arr.filter(
        (item, index, self) => self.indexOf(item) === index
);
console.log(result);// [1, 3, 4, 2, 5, 43]

Array.protype.forEach() 循环迭代

array.forEach(callback[, thisArgs]):undefined
  • callback为数组中每个元素要执行的函数
  • thisArgs可选表示当执行回调函数时用作this的值

回调函数可接受三个参数,分别是curvalindexarray

array.forEach((curval, index, array)=>{}, this)
  • curval表示当前元素,即数组中正在处理的当前元素。
  • index索引,表示数组中当前正在处理的元素的索引值。
  • array表示当前正在操作的数组

例如:

let arr = [
    {id:1, name:"alice", status:1},
    {id:2, name:"bob", status:0},
    {id:3, name:"carl", status:1}
];

let result = [];
arr.forEach((curval, index, arr)=>{
   if(curval.status === 0){
       result.push(curval);
   }
});

console.log(result);

Array.from() 对象转数组

ES6为数组Array新增了from函数用于将对象转换为数组,不过这里的对象是有要求的,Array.from可将两种对象转换为数组。

  • 可迭代对象

部署了Iterator接口的对象,比如ArraySetMap

  • 伪数组对象(类数组对象)

伪数组对象(like-array),即一个对象必须具有length属性和若干索引属性的对象。若对象不具有length属性则转换的结果是一个空数组。

例如:获取列表中元素的文本内容

<ul class="todos">
    <li class="todo">go mountain climbing</li>
    <li class="todo">go to the cliema</li>
    <li class="todo">shopping</li>
</ul>
<script>
    const nodes = document.querySelectorAll(".todo");
    console.log(nodes);
    //NodeList(3) [li.todo, li.todo, li.todo]

    const arr = Array.from(nodes);
    console.log(arr);
    //(3) [li.todo, li.todo, li.todo]

    const contents = arr.map(item=>item.textContent);
    console.log(contents);
    //(3) ["go mountain climbing", "go to the cliema", "shopping"]
</script>

例如:获取列表中文本内容

<ul class="todos">
    <li class="todo">go mountain climbing</li>
    <li class="todo">go to the cliema</li>
    <li class="todo">shopping</li>
</ul>
<script>
    const nodes = document.querySelectorAll(".todo");
    console.log(Array.from(nodes, node=>node.textContent));
    //["go mountain climbing", "go to the cliema", "shopping"]
</script>

函数参数

Array.from(arrayLike[, mapFn[, thisArg]])
  • arrayLike被转换的类数组对象

例如:数值求和

function sum(){
    return Array
        .from(arguments)
        .reduce((preval, curval)=>preval + curval, 0);
}
console.log(sum(1, 2, 3, 4));//10
  • mapFn映射函数,用于对转换中每个元素进行加工,并将加工后的结果作为数组的元素组。

例如:创建随机数的数组

const makeRandomArray = (len,min,max)=>Array.from(
    {length:len},
    (v,k)=>(Math.random()*(max-min+1)|0) + min
);
console.log(makeRandomArray(10, 0, 100));
//(10) [59, 38, 1, 62, 75, 89, 86, 34, 43, 47]
  • thisArg映射函数中this所指向的对象

立刻将被处理的数据和处理对象分离,将不同的处理数据的方法封装到不同的对象中,处理方法可采用相同的名称。调用Array.from对数据对象进行转换时,可以将不同的处理对象按照实际情况进行注入,以得到不同的结果,适合解耦。这种做法类似于模板设计模式的引用,也类似于依赖注入。

应用场景

  • 转换字符串
    Array.from可将ASCII码字符串拆解为一个数据,也可以将Unicode字符串拆解为数组。
console.log(Array.from("hello"));
//(5) ["h", "e", "l", "l", "o"]

console.log(Array.from('\u767d\u8272\u7684\u6d77'));
//(4) ["白", "色", "的", "海"]
  • 转换类数组对象

类数组对象必须具有length属性,如果没有length属性则转换结果为空数组。同时元素属性名必须是数值或是可以转化为数值的字符,即属性名代表数组的索引值。若没有索引值则转换对应的元素会为空。

const obj = {};
obj[0] = 1;
obj["1"] = "alpha";
obj[""] = 3.14;
obj["length"] = 3;
console.log(obj);//{0: 1, 1: "alpha", "": 3.14, length: 3}
console.log(Array.from(obj));//(3) [1, "alpha", undefined]

Array.of() 将参数转换为数组

Array.of函数的出现是为了弥补数组构造函数的不足,当Array构造方法参数大于一个时才会返回由参数组成的数组。当Array构造函数的参数只有一个时,实际上是指定数组的长度。

console.log(Array());//[]
console.log(Array(3));//(2) [empty × 2]
console.log(Array(1, 2));//(2) [1, 2]

Array.of方法总会返回由参数组成的数组,若没有参数则返回空数组。

console.log(Array.of());//[]
console.log(Array.of(1));//[1]
console.log(Array.of(1,2));//(2) [1, 2]

Array.of方法可使用原生代码进行模拟

function arrayOf(){
    return [].slice.call(arguments);
}
console.log(arrayOf());//[]
console.log(arrayOf(1));//[1]
console.log(arrayOf(1,2));//(2) [1, 2]

Array.protype.find() 查找元素

Array.find方法用于返回通过测试函数的数组的第一个元素的值。

Array.find方法会为数组中每个元素都调用一次函数执行,当数组中元素在测试条件返回true时,find方法返回符合条件的元素,之后的值不会再调用执行函数。如果没有符合条件的元素则返回undefined

例如:获取数组中第一个偶数值

const arr = [5, 12, 9, 10];
const ret = arr.find((ele)=>ele % 2 === 0);
console.log(ret);//12

find参数

array.find(callback[, thisValue])
  • callback 必需,执行函数,数组中每个元素都需要执行的函数。
  • thisValue 可选,传递给函数的this值。若为空则会传入undefinedthis值。

callback执行函数参数

array.find(function(currentValue, index, arr), thisValue)
  • currentValue 必须,表示当前数组元素。
  • index 可选表示当前元素的索引值
  • arr 可选,表示当前元素所属的数组对象。
const arr = [
    {id:1, name:"Alpha"},
    {id:2, name:"Beta"},
    {id:3, name:"Gamma"},
    {id:4, name:"Delte"}
];

const ret = arr.find(
    (ele, idx, arr)=>ele.name.toLowerCase()==="beta"
);
console.log(ret);//{id: 2, name: "Beta"}

Array.protype.findIndex() 查找索引

数组原型的findIndex()方法用于查找指定数组中满足测试函数条件的第一个索引,若不存在则返回-1。

array.findIndex(callback[, thisArg])

findIndex参数列表

  • callback必需,针对数组中每个元素都会执行的回调函数
  • thisArg 可选,执行回调函数时作为this对象的值。
const arr = [
    {id:1, name:"Alpha"},
    {id:2, name:"Beta"},
    {id:3, name:"Gamma"},
    {id:4, name:"Delte"}
];

const idx = arr.findIndex(
    ele=>ele.name.toLowerCase()==="beta"
);
console.log(idx);//1

Array.protype.some() 是否至少有一个元素符合条件

some()方法测试是否数组中至少有一个元素可以通过被提供的函数方法,若满足则返回布尔值true否则返回false

arr.some(callback(element, index, array), thisArg)

例如:判断数组中是否函数键值为beta的元素

const arr = [
    {id:1, name:"Alpha"},
    {id:2, name:"Beta"},
    {id:3, name:"Gamma"},
    {id:4, name:"Delte"}
];

const bool = arr.some(
    ele=>ele.name.toLowerCase()==="beta"
);
console.log(bool);//true

Array.protype.every() 是否所有元素都符合条件

every()方法用于测试一个数组中所有元素是否都能通过指定回调函数的测试,若满足则返回布尔值true否则返回false

arr.every(callback(element, index, array), thisArg)

例如:判断所有用户状态是否都正常

const arr = [
    {id:1, name:"Joe", status:1},
    {id:2, name:"Ally", status:1},
    {id:3, name:"Rex", status:1},
    {id:4, name:"Jake", status:0}
];

const bool = arr.every(
    ele=>ele.status===1
);
console.log(bool);//false

循环

  • ES5中遍历集合通常使用的是for循环,数组可使用forEach()方法,对象可使用for-in
  • ES6中新增Map和Set集合,因此ES6创造了一个新的遍历命令for...of循环。

循环方式对比

  • for循环书写不便
  • for...in循环效率低下为其它循环1/7
  • forEach循环不能breakreturn
  • ES6新增for...of循环,其特性是内置迭代器,可以用来执行Generator生成器。

for-in

for-in是为对象设计的,用来遍历字符型的键,可以以任意顺序遍历一个对象除了Symbol以外的可枚举的(Enumerable)键。

for(let key in document){
    console.log(key, typeof key, document[key]);
}

for-in循环只能遍历可枚举属性,比如Array、Object、Number等,JS中基本包装类型的原型属性都是不可枚举的,它们使用内置构造函数所创建的对象都会继承自Object.prototypeString.prototype的不可枚举属性。

属性的枚举性会影响到Object.keys()JSON.stringify()方法,它们只能返回对象本身具有的可枚举属性。

for-in循环可以迭代对象所有可枚举属性和从构造函数的prototype继承而来,包括被覆盖的内置属性。

Object.prototype.run = ()=>console.log("object prototype method run");

const result = {code:1, message:"success"};
for(let key in result){
    console.log(key, typeof key, result[key]);
}
//code string 1
//message string success
//run string ()=>console.log("object prototype method run")

由于for-in循环能够枚举继承的属性名,所以在数组中不建议使用。可以使用hasOwnProperty()过滤,因为所有继承Object的对象都会继承到hasOwnProperty(),可使用hasOwnProperty()方法来检测一个对象是否含有特定的自身属性。hasOwnProperty()方法与in运算符不同之处在于,hasOwnProperty()方法会忽略从原型链上继承到的属性。

Object.prototype.run = ()=>console.log("object prototype method run");
const result = {code:1, message:"success"};
for(let key in result){
    //过滤掉对象继承的属性
    if(result.hasOwnProperty(key)){
        console.log(key, typeof key, result[key]);
    }
}
//code string 1
//message string success

//使用Object.keys()方法获取所有的自身可枚举属性组成的数组
console.log(Object.keys(result));// ["code", "message"]
console.log(Object.values(result));// [1, "success"]

为什么不建议使用for-in循环遍历数组呢?

  • 可能会按照随机顺序遍历数组元素
  • 每次循环遍历的索引不是数字而是字符串的索引值
  • for-in遍历对象
const result = {code:1, message:"success"};
result.total = 100;
for(let key in result){
    console.log(key, result[key]);
}
//code 1
//message success
//total 100
  • for-in遍历数组,数组本质上也是一个对象,数组的每个元素的索引可以被视为对象的属性。
//定义数组
const result = [1, "success"];

//为数组添加额外的自定义属性
result.total = 100;

//使用for-in遍历数组
for(let key in result){
    console.log(key, typeof key, result[key]);
}
//0 string 1
//1 string success
//total string 100

//数组长度中并不包含自定义属性
console.log(result.length);//2

for-of

// variable 每个迭代的属性值被分配给该变量
// iterable 一个具有可枚举属性并可迭代的对象
for(variable of iterable){
  statement
}

for...of语句会创建一个循环来遍历可迭代的数据结构,支持数组String、字符串Array、映射Maps、集合Sets等。

迭代流程

for...of循环首先会调用集合的Symbol.iterator方法,紧接着返回一个新的迭代对象,迭代器对象可以有任意具有next()方法的对象。for...of循环将重复调用next()方法,每次循环调用一次。

const arr = [1, 2, 3];

//获取数组的迭代实体对象
const iterator = arr.entries();
console.log(iterator);//Array Iterator {}

//迭代实体对象拥有next方法,可返回一个对象并包含两个属性。
const obj = iterator.next();
console.log(obj);//{value: Array(2), done: false}

//获取当前迭代实体对象的值,形式为[索引, 值]
console.log(obj.value);// [0, 1]

for...of迭代并不支持普通对象。

const obj = {id:1, name:"alpha"};
for(let prop of obj){//Uncaught TypeError: obj is not iterable
    console.log(prop);
}

迭代对象

如果想迭代一个对象的属性,可以使用for...in循环或内置的Object.keys()方法。

const obj = {id:1, name:"alpha"};
for(let prop of Object.keys(obj)){
    console.log(prop, obj[prop]);
}

可见类数组array-like对象转换为数组后使用for...of迭代,但该对象必须具有一个length属性,而且元素可以被索引。

const obj = {0:1, 1:"alpha", length:2};
console.log(Array.from(obj));//[1, "alpha"]
for(let prop of Array.from(obj)){
    console.log(prop);
}

迭代字符串

对字符串执行迭代,每次打印的是每个索引上的字符。

const str = "hello";
for(let letter of str){
    console.log(letter);
}

迭代数组

迭代数组时,for...of输出数组元素的值,for...in输出数组元素的索引。遍历数组时,for...in会遍历自定义属性,for...of则不会。

const arr = [1, 2, 3, 4];
for(let value of arr){
    console.log(value);
}
for(let [index, value] of arr.entries()){
    console.log(index, value);
}

参数对象

可以将一个参数对象Arguments Object看作是一个类数组array-like对象,对应于传递给函数的参数。

function fn(){
    for(let arg of arguments){
        console.log(arg);
    }
}
fn(1, 2, 3, 4);

迭代映射

  • 映射Map对象是保存键值对key-value,对象和原始值可以用来作为键名key和键值value
  • 映射Map对象会根据其插入方式迭代元素,for...of循环将为每次迭代返回一个键值数组。
const entries = [];
entries.push(["id", 1]);
entries.push(["name", "alpha"]);

const map = new Map(entries);

for(let [key, value] of map){
    console.log(key, value);
}

迭代集合

  • 集合Set对象允许存储任意类型的唯一值,这些值可以是原始值或对象。
  • 集合Set对象只是值的集合
  • 集合Set元素的迭代基于其插入顺序
  • 集合Set中值只能发生一次,如果创建一个具有多个相同元素的集合则它仍然会被认为是单个元素。
const values = [];
values.push(1);
values.push(2);
values.push(1);
values.push(3);
console.log(values);//(4) [1, 2, 1, 3]

const set = new Set(values);
console.log(set);//Set(3) {1, 2, 3}

for(let value of set){
    console.log(value);
}

函数

箭头函数

ES5中函数的写法

function function_name(){}

ES6中提供了箭头函数

()=>{}
  • 箭头函数中如果仅有一个参数则可以省略圆括号
  • 箭头函数中如果仅有一个return则可以省略花括号
//ES5函数表达式
var print = function(msg){
    console.log(msg);
};
print("hello print");//hello print

//ES6函数表达式
let println = (msg)=>{
    console.log(msg);
};
println("hello println");//hello println

//ES6中函数若仅有一个参数则可省略圆括号,若没有货仅有一个return则可省略花括号。
let echo = msg=>console.log(msg);
echo("hello echo");//hello echo
let arr = [1,99,4,12,5,64,2];
arr.sort((x,y)=>x-y);
console.log(arr);//[1, 2, 4, 5, 12, 64, 99]

ES6箭头函数的好处

  • 简明的语法
  • 隐式返回
  • 不绑定this

例如:使用ES6的箭头函数改写ES5中的函数

const numbers = [5, 6, 12, 9, 23, 1];

const doubles = numbers.map(function(number){
    return number * 2;
});
console.log(doubles);// [10, 12, 24, 18, 46, 2]
const numbers = [5, 6, 12, 9, 23, 1];

const doubles = numbers.map(number=>number*2);
console.log(doubles);//[10, 12, 24, 18, 46, 2]

箭头函数中的this

ES6的箭头函数不用绑定this

const user = {
    id:1,
    name:"junchow",
    hobbies:["coding", "sleeping", "reading"],
    print:function(){
        this.hobbies.map(function(item){
           console.log(this.id, this.name, item);//undefined "" "coding"
        });
    }
};
user.print();
const user = {
    id:1,
    name:"junchow",
    hobbies:["coding", "sleeping", "reading"],
    print:function(){
        var self = this;
        this.hobbies.map(function(item){
           console.log(self.id, self.name, item);//1 "junchow" "coding"
        });
    }
};
user.print();

ES6中的this值是指向父作用域的,与ES5中this动态指定的有所不同。箭头函数中的this值是作用于词法作用域,也就是在定义时已经被指定了的。

const user = {
    id:1,
    name:"junchow",
    hobbies:["coding", "sleeping", "reading"],
    print:function(){
        this.hobbies.map(item => {
           console.log(this.id, this.name, item);//1 "junchow" "coding"
        });
    }
};
user.print();

箭头函数使用禁忌

在那些情况下不能使用箭头函数呢?

  • 作为构造函数,方法需要绑定对象。
//使用箭头函数作为构造函数
const User = (id,name)=>{
    this.id = id;
    this.name = name;
};
//箭头函数不使用this绑定对象实例
const user = new User(1, "alpha");//Uncaught TypeError: User is not a constructor
const User = function(id,name){
    this.id = id;
    this.name = name;
};
const user = new User(1, "alpha");

//方法绑定对象,无法使用this。
User.prototype.inc = ()=>{
    console.log(this);//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
    this.id++;
};

user.inc();
console.log(user);//User {id: 1, name: "alpha"}
  • 当真的需要this的时候
const btn = document.querySelector(".btn");
btn.addEventListener("click", ()=>{
    this.classList.add("active");//Uncaught TypeError: Cannot read property 'add' of undefined
    setTimeout(()=>this.classList.remove("active"), 200);
});

注意何时使用this何时不适用this

const btn = document.querySelector(".btn");
btn.addEventListener("click", function(){
    this.classList.add("active");
    setTimeout(()=>this.classList.remove("active"), 200);
});
  • 需要使用arguments对象时
const sum = ()=>{
    //Uncaught ReferenceError: arguments is not defined
    return Array.from(arguments).reduce((preval, curval)=>preval + curval, 0);
};
console.log(sum(1, 2, 3, 4));

函数参数

剩余参数Rest Parameter

关键词:收集、展开

JS函数内部存在一个arguments类数组对象,ES6引入一个新对象可以获取除开始参数之外的所有参数,即剩余参数。剩余参数是自定义的一个普通标识符。

函数的剩余参数是指函数的展开或者说是函数的扩展,剩余参数使用三个点...后跟参数名称,剩余参数是一个数组。剩余参数后不能再有别的参数

  • ...可以收集剩余的参数
let test = (...args) => {
    for(let index in args){
        console.log(index, args[index]);
    }
};
test(1,"alice", 18, true);
let test = (id, ...args) => {
    console.log(id);
    for(let index in args){
        console.log(index, args[index]);
    }
};
test(1,"alice", 18, true);
  • ...可以用来展开数组
let arr = [1, 2, 3];
console.log(arr, ...arr);//[1, 2, 3] 1 2 3

let test = (x,y,z) => console.log(x, y, z);

test(1,2,3);//1 2 3
test(...arr);//1 2 3
let arr1 = [1, 3, 5];
let arr2 = [2, 4, 6];
console.log(arr1.concat(arr2));//[1, 3, 5, 2, 4, 6]
console.log([...arr1, ...arr2]);//[1, 3, 5, 2, 4, 6]
  • ...收集并展开函数参数
let test = (...args) => fn(...args);
let fn = (x,y)=>x+y;
console.log(test(1, 2));//3

函数的剩余参数与arguments类数组对象之间有何区别呢?

  • 剩余参数只包含没有对应形参的实参,arguments对象包含传入的所有实参。
  • arguments类数组对象不是一个真正的数组,剩余参数是真正的数组实例。
  • arguments类数组对象拥有附加属性,比如callee属性。
const multiply = (multiplier, ...args)=>args.map((ele)=>ele*multiplier);
console.log(multiply(2, 1,2,3));//(3) [2, 4, 6]

arguments是一个类数组对象,若想使用数组提供的方式,则向需要将类数组对象转换为数组。

function asc(){
    const args = Array.prototype.slice.call(arguments);
    return args.sort((x,y)=>x-y);
}
console.log(asc(11, 2, 42, 5, 23));//(5) [2, 5, 11, 23, 42]

剩余参数可用于变量解构

const info = [10, "joe", 90, 87, 79];
const [id, name, ...scores] = info;
console.log(id, name, scores);//10 "joe" (3) [90, 87, 79]

扩展运算符

扩展运算符Spread Operator...三个点,可用于将数组转化为逗号分隔的参数列表,可见其视为rest剩余参数的逆运算。

const arr = [1, 2, 3];
console.log(arr, ...arr);//(3) [1, 2, 3] 1 2 3

扩展运算符可以通过减少赋值语句的使用,或减少通过下标访问数组或对象的方式,使代码更加简洁优雅,可读性更强。

function fn(x,y){
    console.log(x,y);
}

var args = [10, 20];
//将数组元素迭代为函数参数可使用Function.prototype.apply的方式
fn.apply(null, args);//10 20

扩展运算符...的展开语法可以在函数调用或数组构造时,将数组表达式或字符串在语法层面展开,还可以在构造字面量对象时,将对象表达式按照键值对的方式展开。

function fn(x,y){
    console.log(x,y);
}

var args = [10, 20];
//使用扩展运算符...将展开语法
fn(...args);//10 20

数组的扩展运算符

  • 将数组转换为参数序列
const add = (x,y)=>x+y;
//将数组转换为函数的参数列表
var args = [1,2];
console.log(add(...args));//3
  • 复制数组
    扩展运算符...用于取出参数对象中的所有可遍历属性,然后拷贝到当前对象中。
const arr = [10, 20];
const ret = [...arr];
console.log(ret);//(2) [10, 20]
//当数组中含有空位时转化后的空位将转化为undefined
const arr = [10, , 30];
console.log(arr);//(3) [10, empty, 30]
console.log([...arr]);//(3) [10, undefined, 30]
  • 生成数组
    扩展运算符...与解构赋值结合可以用于生成数组
const arr = [10, "alice", 90, 79, 30];
const [id, name, ...scores] = arr;
console.log(id, name, scores);//10 "alice" (3) [90, 79, 30]
  • 合并数组
    使用扩展运算符...合并数组的本质也是将一个数组转换为使用逗号分隔的参数列表,然后放置到数组中。
const arr = [1, 2];
const ary = [1, 20];

const ret = [...arr, ...ary];
console.log(ret);//(4) [1, 2, 1, 20]

传统合并数组的方式可使用concat函数

const male = ["joe", "jake"];
const female = ["lvy", "amy"];
female.push("anne");

const ret = male.concat(female);
console.log(ret);//(5) ["joe", "jake", "lvy", "amy", "anne"]

传统合并数组的方式还可以使用apply方法来实现

const male = ["joe", "jake"];
const female = ["lvy", "amy"];

male.push.apply(male, female);
console.log(male);//(4) ["joe", "jake", "lvy", "amy"]
  • 将字符串转化为字符数组
console.log([..."hello"]);//(5) ["h", "e", "l", "l", "o"]
  • 任何Iterator可迭代接口的对象都可以使用扩展算符转化为数组
function fn(){
    console.log([...arguments]);
}
fn(1, 2, 3);//(3) [1, 2, 3]

对象的扩展运算符

对象中的扩展运算符...用于提取出参数对象中所有的可遍历属性并拷贝到当前对象中。

const user = {id:1, name:"alice"};
const obj = {...user};
console.log(obj);//{id: 1, name: "alice"}

对象中使用扩展运算符的方式实际等价于调用Object.assign()方法用于对象的合并,并将原对象的所有可枚举属性复制到目标对象中。

const user = {id:1, name:"alice"};
const obj = Object.assign({}, user);
console.log(obj);//{id: 1, name: "alice"}

用户自定义属性若放在扩展运算符...后面,则扩展运算符内部的同名属性会被覆盖掉。

const user = {id:1, name:"alice"};
const obj = {id:19, nick:"anne"};
console.log({...user, ...obj});//{id: 19, name: "alice", nick: "anne"}

扩展运算符对对象实例的拷贝属于一种浅拷贝,

使用扩展运算符批量获取HTML标签文本内容

const nodes = document.querySelectorAll(".list-group-item");
const arr = Array.from(nodes);
//const arr = [...nodes];
const contents = arr.map(item=>item.textContent);
console.log(contents);//(3) ["lisa", "lvy", "alice"]

使用扩展运算符删除数组中指定元素

const list = [
    {id:1, name:"alice"},
    {id:2, name:"ben"},
    {id:3, name:"carl"},
    {id:4, name:"denny"},
    {id:5, name:"elva"},
    {id:6, name:"fifi"},
];

const id = 3;//待删除指定id的元素
const index = list.findIndex(item=>item.id === id);
const result = [
    ...list.slice(0, index), 
    ...list.slice(index+1, list.length)
];
console.log(result);

参数默认值

ES5中函数的默认值

function fn(x, y){
    x = x || 1;
    y = y || 2;
    return x + y;
}
console.log(fn());//3

ES6中函数参数的默认值

const fn = (x=1, y=2)=>x + y;
console.log(fn());//3
console.log(fn(10));//12
console.log(fn(undefined, 20));//21 需要手动指定为undefined
console.log(fn(10, 20));//30

面向对象

ES6引入了Class类的概念,类作为对象的模板,通过class关键字可以定义类。事实上ES6中的class可以看作只是一种语法糖,使用class写法让JS对象原型的写法更加清晰,更像面向对象编程的语法。

  • ES6虽然引入了class关键字但并没有真正引入类的概念,通过class定义的类仍然是函数。
  • class仅仅是通过简单直观的语法去实现了原型链继承
class User{
    constructor(id, name){
        this.id = id;
        this.name = name;
    }
    toString(){
        console.log(`id = ${this.id} name = ${this.name}`);
    }
}
console.log(typeof User);//function
let user = new User(1, "junchow");
user.toString();//id = 1 name = junchow

构造方法

  • constructor方法是类的默认方法,通过new命令生成对象实例,自动调用该方法。
  • 类必须有constructor方法,如果没有显式定义,默认会添加一个空的constructor
  • this关键字表示类的实例对象

存取方法

ES6中类的内部可以使用getset关键字,对某个属性设置存值方法和取值方法,用来拦截该属性的存取行为。

静态方法

面向对象语言中,静态方法是指无需实例化,可通过类名直接调用的方法,静态方法不会继承到类的实例中,因此静态方法经常用来作为工具函数,例如典型的Math.random()方法。

class User{
    constructor(id, name){
        this.id = id;
        this.name = name;
    }
    toString(){
        console.log(`id = ${this.id} name = ${this.name}`);
    }
    static run(){
        console.log("user run");
    }
}
User.run();//user run
  • ES6类定义中使用static关键字来定义静态方法
  • static关键字是ES6的另一个语法糖,static使静态方法声明也成为了一个一等公民。
  • 静态方法可以从子类的super对象上调用
class User{
    constructor(id, name){
        this.id = id;
        this.name = name;
    }
    static run(){
        console.log("user run");
    }
}
class Member extends User{
    static run(){
        super.run();
        console.log("member run");
    }
}
Member.run();
//user run
//member run

静态属性

静态属性是指类本身的属性,即Class.propName,而不是定义在实例对象this上的属性。ES6中明确规定类的内部只具有静态方法不具有静态属性。

ES6中的静态属性只能通过类直接点方法赋值的方式来实现。

class Klass{
  constructor(){}
}
Klass.propName = 0;

对象字面量

  • ES6对象字面量简写
const id = 1;
const name = "alice";
//传统方式
const obj = {id:id, name:name};
console.log(obj);//{id: 1, name: "alice"}
//简写方式
const obj1 = {id, name};
console.log(obj1);//{id: 1, name: "alice"}
  • ES6返回对象简写
//传统方式
function fn(){
    const id = 1;
    const name = "alice";
    return {id:id, name:name};
}
console.log(fn());//{id: 1, name: "alice"}
//简写方式
function func(){
    const id = 1;
    const name = "alice";
    return {id, name};
}
console.log(func());//{id: 1, name: "alice"}
  • ES6对象方法简写
    对象不仅可以保存数据,也可以用来包含函数。
//传统方式
const id = 1;
const name = "alice";
const user = {
    id:id,
    name:name,
    print:function(){
        console.log(`id=${this.id} name=${this.name}`);
    }
};
user.print();//id=1 name=alice

//简写方式
const id = 1;
const name = "alice";
const user = {
    id:id,
    name:name,
    print(){
        console.log(`id=${this.id} name=${this.name}`);
    }
};
user.print();//id=1 name=alice
  • ES6计算属性
    ES5中如果属性名是变量或需动态计算则只能通过对象.[变量名]的方式访问,而这种动态计算属性名在字面量中是无法使用的。
const attrname = "nickname";
const user = {id:1, nickname:"alice"};
console.log(user[attrname]);//alice

const attrname = "nickname";
const user = {id:1, nickname:"alice", attrname:"attr"};
console.log(user[attrname]);//alice

ES6中将属性名使用方括号[]包裹后括号中就可以引用提前定义的变量了。

const attrname = "nickname";
const user = {id:1, nickname:"alice", [attrname]:"attr"};
console.log(user[attrname]);//attr
let id = 0;
const uids = {
    [`uid-${++id}`]:id,
    [`uid-${++id}`]:id,
    [`uid-${++id}`]:id,
};
console.log(uids);//{uid-1: 1, uid-2: 2, uid-3: 3}
const keys = ["id", "name", "status"];
const values = [1, "alice", true];
const obj = {
  [keys.shift()]:values.shift(),
  [keys.shift()]:values.shift(),
  [keys.shift()]:values.shift(),
};
console.log(obj);//{id: 1, name: "alice", status: true}

对象解构

ES6添加解构的新特性,解构是一种打破数据结构,将其拆分为更小部分的过程。

  • 解构赋值表达式若指定的局部变量名称在对象中不存在则会被赋值为undefined
  • 当指定属性不存在时可以为其指定默认值
const result = {
    code:1,
    message:"success",
    data:{
        total:10,
    }
};
const {code, message, error, msg="ok"} = result;
console.log(code, message, error, msg);//1 "success" undefined "ok"
  • 在定义变量之后使用对象解构赋值则需要添加小括号

JS引擎会将花括号视为一个代码块,代码块是不允许出现在赋值语句的左侧。使用小括号包裹解构赋值语句,添加小括号后可以将块语句转换为表达式,从而实现整个解构赋值过程。

const result = {
    code:1,
    message:"success",
    data:{
        total:10,
    }
};

let code, message;
({code, message} = result);
console.log(code, message);//1 "success"
  • ES6扩展语法可以为非同名局部变量赋值,即是用不同命名的局部变量来存储对象属性的值。
  • 为非同名局部变量赋值时也可以指定默认值
const result = {
    code:1,
    message:"success",
    data:{
        total:10,
    }
};
//重命名
const {code:error, message:msg="ok"} = result;
//console.log(code);//Uncaught ReferenceError: code is not defined
console.log(error, msg);//1 "success"
const result = {
    code:1,
    message:"success",
    data:{
        total:10,
    }
};
//变量定义
const code = 1000;
const message = "error";
//重命名
const {code:error, message:msg="ok"} = result;

console.log(code, message);//1000 "error"
console.log(error, msg, amount);//1 "success" 10
  • 嵌套对象解构与对象字面量语法类似,可以将对象拆解以获取想要的值。
const result = {
    code:1,
    message:"success",
    data:{
        total:10,
    }
};
const {code:error, message:msg="ok", data:{total:amount=0}} = result;
console.log(error, msg, amount);//1 "success" 10

继承

extends

ES6使用extends关键字继承父类所有属性和方法,从而避免使用Object.create().protoObject.setPrototypeOf()等繁杂的扩展方式。

super

ES6使用super关键字来调用父类的成员,super即可作为函数调用也可以作为对象使用。

  • super作为函数调用

ES6规定子类的构造函数必须执行一次super函数,ES6中super作为函数调用时代表父类的构造函数,返回的是子类的实例,即super内部this指代的是子类,因此super()实际相当于Base.prototype.constructor.call(this)

class Base{
    constructor(){}
}
class Child extends Base{
    constructor(){
        super();// Base.prototype.constructor.call(this);
    }
}
  • super作为对象

super作为对象时指向的是父类的原型对象,在普通方法中使用super.propsuper.method()相当于一个指向对象的[{Prototype}]的属性。

class Base{
    constructor(){}
    tostring(){
        console.log("base tostring");
    }
}
class Child extends Base{
    constructor(){
        super();
        super.tostring();
    }
}
let child = new Child();//base tostring

面向对象应用 React

  • 模块化、组件化class
  • JSX = babel = browser.js

例如:使用forEach

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>react</title>
    <script src="https://cdn.bootcss.com/react/16.9.0-alpha.0/umd/react.development.js"></script>
    <script src="https://cdn.bootcss.com/react-dom/16.9.0-alpha.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.bootcss.com/babel-standalone/7.0.0-beta.3/babel.js"></script>
    <script type="text/babel">
        //开发组件,一切组件都是可见的
        class List extends React.Component{
            constructor(...args){
                super(...args);
            }
            render(){
                let elements = [];
                let items = this.props.items;
                if(items !== ""){
                    items.forEach((item, index)=>{
                        elements.push(<ListItem key={index} val={item}></ListItem>);
                    });
                }
                return <ul>{elements}</ul>;
            }
        }
        class ListItem extends React.Component{
            //构造器
            constructor(...args){
                super(...args);
            }
            //必须
            render(){
                let val = this.props.val;
                return <li>{val}</li>;
            }
        }
        //页面渲染
        window.onload = () => {
            let element = <List items={["alice","bob"]}></List>;
            let app = document.querySelector("#app");
            ReactDOM.render(element, app);
        };
    </script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

例如:使用ES6的map映射

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>react</title>
    <script src="https://cdn.bootcss.com/react/16.9.0-alpha.0/umd/react.development.js"></script>
    <script src="https://cdn.bootcss.com/react-dom/16.9.0-alpha.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.bootcss.com/babel-standalone/7.0.0-beta.3/babel.js"></script>
    <script type="text/babel">
        //开发组件,一切组件都是可见的
        class List extends React.Component{
            constructor(...args){
                super(...args);
            }
            render(){
                //使用map映射
                return <ul>{this.props.items.map((item, index)=><ListItem key={index} val={item}></ListItem>)}</ul>;
            }
        }
        class ListItem extends React.Component{
            //构造器
            constructor(...args){
                super(...args);
            }
            //必须
            render(){
                let val = this.props.val;
                return <li>{val}</li>;
            }
        }
        //页面渲染
        window.onload = () => {
            let element = <List items={["alice","bob"]}></List>;
            let app = document.querySelector("#app");
            ReactDOM.render(element, app);
        };
    </script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

JSON

  • JSON对象
  • 名字一样
  • 方法简写

JSON标准写法

  • 只能使用双引号
  • 所有的名字都必须使用双引号包裹
{"id":1,"name":"alpha"}

JSON串行化

let json = {id:1, name:"alpha"};
let str = JSON.stringify(json);
console.log(str);//{"id":1,"name":"alpha"}

let uri = encodeURIComponent(str);
console.log(uri);//%7B%22id%22%3A1%2C%22name%22%3A%22alpha%22%7D

JSON反串行化

let uri = "%7B%22id%22%3A1%2C%22name%22%3A%22alpha%22%7D";
let str = decodeURIComponent(uri);
console.log(uri);//{"id":1,"name":"alpha"}

let json = JSON.parse(str);
console.log(json, json.id, json.name);//{id: 1, name: "alpha"} 1 "alpha"

JSON简写

let id = 1;
let name = "alpha";
//JSON中名字和值一样时可以简写
let json = {id, name, "status":0};
console.log(json);//{id: 1, name: "alpha", status: 0}

//JSON中方法简写
json = {
    id,
    name,
    init(){
        console.log(id, name);
    }
};
json.init();

Promise 承诺

  • Promise的中文含义是承诺

异步 & 同步

  • 异步:操作之间没有关系,可以同时并发的进行多个操作。异步的性能更高,但代码更加复杂。
  • 同步:操作之间具有顺序先后关系,同时只能执行一个操作。

例如:使用Axios库的Promise实现AJAX的同步调用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>axios</title>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/dompurify/1.0.11/purify.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    //axios对象的方法将返回一个Promise对象,因此可使用连缀的方式继续调用then方法进行处理。
    axios.get("data1.json").then((response=>{
        const data = response.data;
        const id = data.id;
        return axios.get(`data2.json?id=${id}`);//返回Promise
    })).then(response=>{
        console.log(response.data);//获取data2.json中的数据
    }).catch(error=>{
       console.error(error);
    });
</script>
</body>
</html>

Promise可以消除异步操作,本质上是使用同步方式编写异步操作。

const promise = new Promise((resolve, reject)=>{
  //模拟异步操作
  setTimeout(()=>{
    //resolve("success");//成功返回执行then
    reject(Error("error"));//失败返回执行catch,调用Error()方法用于指定错误出现的行数。
  }, 2000);
});
promise.then(data=>{
  console.log(data);//success
}).catch(error=>{
  console.error(error);//error
});

resolve

const promise = new Promise((resolve, rejecct)=>{
  resolve("success");
});
promise.then(data=>{
  console.log(data);//success
});
const clubs = [
  {id:1, owner:"joe", status:1},
  {id:2, owner:"lvy", status:0},
  {id:3, owner:"ben", status:1}
];
const players = [
  {id:1, username:"joe", account:124314},
  {id:1, username:"charly", account:424614},
  {id:1, username:"mary", account:994334},
];
//通过俱乐部ID获取记录
function getClubById(club_id){
  //返回Promise对象
  return new Promise((resolve, reject)=>{
    //模拟异步延迟查找
    setTimeout(()=>{
      const obj = clubs.find(item=>item.id === club_id);
      console.log(obj);
      if(obj){
        resolve(obj);
      }else{
        reject(Error("no club found"));
      }
    }, 2000);
  })
}
//根据用户名获取对象
function getPlayerByClub(club){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      const player = players.find(item=>item.username === club.owner);
      if(player){
        club.player = player;
        console.log(club);
        resolve(club);
      }else{
        reject(Error("no player found"));
      }
    }, 1000);
  });
}

getClubById(1).then(response=>{
  console.log(response);
  return getPlayerByClub(response);
}).then(response=>{
  console.log(response);
}).catch(error=>{
  console.error(error);
});

then

例如:使用Promise

$ vim data.json
{
    "id":"1",
    "name":"junchow"
}
$ vim test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>es6</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="es6.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>
$ vim es6.js
let promise = new Promise((resolve, reject)=>{
    //异步代码 resolve成功 reject失败
    $.ajax({
        url:"data.json",
        dataType:"json",
        type:"post",
        success:(res)=>{
            resolve(res);
        },
        error:(err)=>{
            reject(err);
        }
    })
});
promise.then((res)=>{
    //resolve
    console.log(res);//{id: "1", name: "junchow"}
}, (err)=>{
    //reject
    console.log(err);
});

all

例如:使用Promise异步执行多个操作

$ vim data1.json
{
    "id":"1",
    "name":"junchow"
}
$ vim data2.json
{
  "id":2,
  "name": "alice"
}
$ vim test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>es6</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="es6.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>
$ vim es6.js
let promise1 = new Promise((resolve, reject)=>{
    //异步代码 resolve成功 reject失败
    $.ajax({
        url:"data1.json",
        dataType:"json",
        type:"post",
        success:(res)=>{
            resolve(res);
        },
        error:(err)=>{
            reject(err);
        }
    })
});
let promise2 = new Promise((resolve, reject)=>{
    //异步代码 resolve成功 reject失败
    $.ajax({
        url:"data2.json",
        dataType:"json",
        type:"post",
        success:(res)=>{
            resolve(res);
        },
        error:(err)=>{
            reject(err);
        }
    })
});
Promise.all([promise1, promise2]).then((arr)=>{
    //全部成功 resolve
    console.log(arr);//[{id: "1", name: "junchow"},{id: 2, name: "alice"}]
    let [res1, res2] = arr;
    console.log(res1, res2);//{id: "1", name: "junchow"} {id: 2, name: "alice"}
}, (err)=>{
    //至少一个失败 reject
    console.log(err);
});

封装Promise

$ vim es6.js
let createPromise = (url, type="GET", data = null)=>new Promise((resolve, reject)=>{
    $.ajax({
        url,
        type,
        data,
        dataType:"json",
        success:(res)=>resolve(res),
        error:(err)=>reject(err)
    });
});

let promise1 = createPromise("data1.json");
let promise2 = createPromise("data2.json");
Promise.all([promise1, promise2]).then((arr)=>{
    //全部成功 resolve
    let [res1, res2] = arr;
    console.log(res1, res2);//{id: "1", name: "junchow"} {id: 2, name: "alice"}
}, (err)=>{
    //至少一个失败 reject
    console.log(err);
});

jQuery高版本自带Promise

Promise.all([
    $.ajax({url:"data1.json", dataType:"json"}),
    $.ajax({url:"data2.json", dataType:"json"})
]).then((arr)=>{
    let [res1, res2] = arr;
    console.log(res1, res2);//{id: "1", name: "junchow"} {id: 2, name: "alice"}
},(err)=>{
    console.log(err);
});

race 竞速

race只要有一个成功即返回

Promise.race([
    $.ajax({url:"data1.json", dataType:"json"}),
    $.ajax({url:"data2.json", dataType:"json"})
]).then((arr)=>{
    console.log(arr);//{id: "1", name: "junchow"}
},(err)=>{
    console.log(err);
});

Generator 生成器

  • 普通函数:一撸到底 中间不停留
  • 生成器函数:中间能停留

使用方式

  • 生成器函数需要添加*星号标识,星号可以添加在function*,可以位于中间function * fn,也可以跟在函数名前function *fn
  • 生成器函数内部需要添加yield关键字,表示停留的位置,yield可以理解为暂时放弃。
function *fn(){
    console.log("begin");
    yield;
    console.log("end");
}

let genObj = fn();
console.log(genObj);//fn {<suspended>}
genObj.next();//begin
genObj.next();//end

Generator生成器函数的本质是将一个函数切分(生成)为多个函数,每个next方法执行实际上执行的是生成器生成的子函数。

yield 暂停放弃

  • yield传参
function *fn(){
    console.log("begin");
    let arg = yield;
    console.log(arg);
}

let genObj = fn();
console.log(genObj);//fn {<suspended>}
genObj.next(1);//begin
genObj.next(2);//2

第一个next方法传入的参数执行到yield时是无法获取的。

function *fn(id){
    console.log(id);
    let arg = yield;
    console.log(arg);
}

let genObj = fn(0);
console.log(genObj);//fn {<suspended>}
genObj.next(1);//0
genObj.next(2);//2
  • yield返回
function *fn(id){
    console.log(id);
    let arg = yield id;
    console.log(arg);
    return id * 10;
}

let genObj = fn(10);
console.log(genObj);//fn {<suspended>}

let res1 = genObj.next(1);//10
console.log(res1);//{value: 10, done: false}

let res2 = genObj.next(2);//2
console.log(res2);//{value: 100, done: true}