ES6学习笔记——变量申明、字符串与正则、函数

1.课程基本概述

  1. ECMAScript、JavaScript、NodeJs,它们的区别是什么?

ECMAScript:简称ES,是由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)按照ECMA-262和ISO/IEC 16262标准制定的一种脚本语言规范;

JavaScript:运行在浏览器端的语言,该语言是使用ES标准。 ES+webapi=JavaScript;
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现;

NodeJs:运行在服务器端的语言,该语言使用ES标准。 ES + node api = NodeJs;

  1. ECMAScript有哪些关键的版本?

ES3.0: 1999——成为 JavaScript 的通行标准,得到了广泛支持;
ES5.0: 2009——该版本成为了 ISO 国际标准(ISO/IEC 16262:2011);
ES6.0: 2015——并且更名为“ECMAScript 2015”,TC39委员会决定每年发布一个ECMAScript 的版本;
ES7.0: 2016
等等…

2.变量声明:var let const

2-1.使用var申明变量

1.1 允许重复的变量声明:导致数据被覆盖
1.2 变量提升:怪异的数据访问、闭包问题
1.3 全局变量挂载到全局对象:全局对象成员污染问题

var div = document.getElementById("divBtn");
for (var i = 1; i <= 10; i++) {
     var btn = document.createElement("button");
     btn.innerHTML = "按钮" + i;
     div.appendChild(btn);
     btn.onclick = function () {
      		console.log(i); //每次点击都会输出11
     	  }
       }
2-2.使用let申明变量

ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念;
块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域;

1.1 let声明的变量不会挂载到全局对象;
1.2 let声明的变量,不允许当前作用域范围内重复声明,在块级作用域中用let定义的变量,在作用域外不能访问;
1.3 使用let不会有变量提升,因此,不能在定义let变量之前使用它;

底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access ‘a’ before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。

在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量

在循环中使用let声明的循环变量,在循环结束后会销毁;

let div = document.getElementById("divBtn");
for (let i = 1; i <= 10; i++) {
    let button = document.createElement("button");
    button.innerHTML = "按钮" + i;
    div.appendChild(button)
    button.onclick = function () {
        console.log(i) //使用的是当前作用域中的i
    }  
}
2-3.使用const申明常量

const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。
实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:

  1. 根据经验,开发中的很多变量,都是不会更改,也不应该更改的。
  2. 后续的很多框架或者是第三方JS库,都要求数据不可变,使用常量可以一定程度上保证这一点。

注意的细节:

  1. 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
  2. 常量的命名
    2.1. 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
    2.2. 普通的常量:使用和之前一样的命名即可
  3. 在for循环中,循环变量不可以使用常量

3.字符串与正则

3-1.更好的Unicode支持

早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字。我们将一个16位的二进制编码叫做一个码元(Code Unit)。

后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)。

ES6为了解决这个困扰,为字符串提供了方法:codePointAt,根据字符串码元的位置得到其码点。

同时,ES6为正则表达式添加了一个flag: u,如果添加了该配置,则匹配时,使用码点匹配。

const text = ""; //占用了两个码元(32位)

console.log("字符串长度:", text.length);
console.log("使用正则测试:", /^.$/u.test(text));
console.log("得到第一个码元:", text.charCodeAt(0));
console.log("得到第二个码元:", text.charCodeAt(1));

//:\ud842\udfb7
console.log("得到第一个码点:", text.codePointAt(0));
console.log("得到第二个码点:", text.codePointAt(1));

/**
 * 判断字符串char,是32位,还是16位
 * @param {*} char 
 */
function is32bit(char, i) {
    //如果码点大于了16位二进制的最大值,则其是32位的
    return char.codePointAt(i) > 0xffff;
}

/**
 * 得到一个字符串码点的真实长度
 * @param {*} str 
 */
function getLengthOfCodePoint(str) {
    var len = 0;
    for (let i = 0; i < str.length; i++) {
        //i在索引码元
        if (is32bit(str, i)) {
            //当前字符串,在i这个位置,占用了两个码元
            i++;
        }
        len++;
    }
    return len;
}

console.log("是否是32位的:", is32bit("", 0))
console.log("abab的码点长度:", getLengthOfCodePoint("abab"))
3-2. ES6新增的字符串API(均为字符串的实例(原型)方法)
  • includes
    判断字符串中是否包含指定的子字符串

  • startsWith
    判断字符串中是否以指定的字符串开始

  • endsWith
    判断字符串中是否以指定的字符串结尾

  • repeat
    将字符串重复指定的次数,然后返回一个新字符串。

const text = "他可是个狠人";

console.log("是否包含“狠”:", text.includes("狠"));
console.log("是否以“他”开头:", text.startsWith("他"));
console.log("是否以“狠人”结尾:", text.endsWith("狠人"));
console.log("重复4次:", text.repeat(4));
3-3.正则表达式中的粘连标记

标记名:y

含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置必须在lastIndex位置。

const text = "Hello World!!!";

const reg = /W\w+/y;
reg.lastIndex = 3; //可以动态修改lastIndex的值
console.log("reg.lastIndex:", reg.lastIndex)
console.log(reg.test(text))
3-4.模板字符串

在ES6中,提供了模板字符串的书写,可以非常方便的换行和拼接,要做的,仅仅是将字符串的开始或结尾改为 ` 符号;

如果要在字符串中拼接js表达式,只需要在模板字符串中使用${JS表达式}
表达式可以是任何有意义的数据${1 + 3 * 2 / 0.5}
表达式也是可以嵌套的:${`表达式中的模板字符串${love1 + love2}`};

var love1 = "秋葵";
var love2 = "香菜";
var text = `老邓头喜欢${love1},也喜欢${love2}
\n\n
在模板字符串中也能使用\${JS表达式}可以进行插值`;
console.log(text);
3-5.模板字符串标记
var text = String.raw`abc\t\nbcd`; // String.raw 获取一个模板字符串的原始字符串的
console.log(text);  // "abc\t\nbcd"

在模板字符串书写之前,可以加上标记:

 标记名`模板字符串`

标记是一个函数,函数参数如下:

  1. 参数1:被插值分割的字符串数组
  2. 后续参数:所有的插值

Html结构:

<p>
     <textarea id="txt">textarea>
     <button id="btn">设置div的内容button>
p>
<div id="container">div>

JS代码:

const container = document.getElementById("container");
const txt = document.getElementById("txt");
const btn = document.getElementById("btn");
btn.onclick = function(){
    container.innerHTML = safe`

${txt.value}

${txt.value}

`
; } function safe(parts){ const values = Array.prototype.slice.apply(arguments).slice(1); let str = ""; for (let i = 0; i < values.length; i++) { const v = values[i].replace(/, "<").replace(/>/g, ">"); str += parts[i] + v; if (i === values.length - 1) { str += parts[i + 1]; } } return str; }

4.函数

4-1.参数默认值

在书写形参时,直接给形参赋值,赋的值即为默认值

这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是undefined),则会自动使用默认值。

function sum(a, b = 1, c = 2) {
    return a + b + c;
}

console.log(sum(10, undefined, undefined)) //13
console.log(sum(11))  //14
console.log(sum(1, undefined, 5)) //7
[扩展]对arguments的影响

只要给函数加上参数默认值,该函数会自动变成严格模式下的规则:arguments和形参脱离

[扩展]留意暂时性死区

形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区。

function test(a, b = 1) {
    console.log("arugments", arguments[0], arguments[1]); //arugments 1 2
    console.log("a:", a, "b:", b); //a: 1 b: 2
    a = 3;
    console.log("arugments", arguments[0], arguments[1]); //arugments 1 2
    console.log("a:", a, "b:", b); //a: 3 b: 2
}
test(1, 2);
4-2.剩余参数

arguments的缺陷:

  1. 如果和形参配合使用,容易导致混乱;
  2. 从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图;

ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
语法:

function (...形参名){

}

细节:

  1. 一个函数,仅能出现一个剩余参数
  2. 一个函数,如果有剩余参数,剩余参数必须是最后一个参数
function sum(...args) {
    //args收集了所有的参数,形成的一个数组
    let sum = 0;
    for (let i = 0; i < args.length; i++) {
        sum += args[i];
    }
    return sum;
}
console.log(sum())            //0
console.log(sum(1))           //1
console.log(sum(1, 2))        //3
console.log(sum(1, 2, 3))     //6
console.log(sum(1, 2, 3, 4))  //10
4-3.展开运算符

使用方式:...要展开的东西
MDN文档参考

/**
 * 对所有数字求和
 * @param  {...any} args 
 */
function sum(...args) {
    let sum = 0;
    for (let i = 0; i < args.length; i++) {
        sum += args[i];
    }
    return sum;
}

/**
 * 获取一个指定长度的随机数组成的数组
 * @param {*} length 
 */
function getRandomNumbers(length) {
    const arr = [];
    for (let i = 0; i < length; i++) {
        arr.push(Math.random());
    }
    return arr;
}

const numbers = getRandomNumbers(10);
//将数组的每一项展开,依次作为参数传递,而不是把整个数组作为一个参数传递
// sum(numbers)
console.log(sum(...numbers))//相当于传递了10个参数
console.log(sum(1, 3, ...numbers, 3, 5))
针对数组展开——ES6
const arr1 = [3, 67, 8, 5];

//数组拷贝(copy)
const arr2 = [...arr1];         //[3, 67, 8, 5]
const arr2 = [0, ...arr1, 1];  //[0, 3, 67, 8, 5, 1]
针对对象展开——ES7
const obj1 = {
    name: "成哥",
    age: 18,
    love: "邓嫂",
    address: {
        country: "中国",
        province: "黑龙江",
        city: "哈尔滨"
    }
}
// 浅克隆到obj2
const obj2 = {
    ...obj1,
    name: "邓哥"
};
console.log(obj2)
console.log(obj1.address === obj2.address)  //true  浅克隆,不会深度拷贝
const obj1 = {
    name: "成哥",
    age: 18,
    loves: ["邓嫂", "成嫂1", "成嫂2"],
    address: {
        country: "中国",
        province: "黑龙江",
        city: "哈尔滨"
    }
}
// 深克隆到obj2       此情况是在知道obj1数据前提下进行拷贝
const obj2 = {
    ...obj1,
    name: "邓哥",
    address: {
        ...obj1.address    //对address进行深度克隆
    },
    loves: [...obj1.loves, "成嫂3"]  //对loves进行深度克隆
};
console.log(obj2)  
console.log(obj1.loves === obj2.loves)   //false
4-4.展开运算符与剩余参数练习

4-4.1

function test(a, b, c) {
    console.log(a, b, c);
}
test(2, 6, 7);  //2 6 7
const arr = ["asf", "Gfh", "111"];
test(...arr);  // 把arr数组拆开作为参数传入;   asf Gfh 111 

4-4.2

<div>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
        <p><input type="number" name="" id="" value="0">p>
    div>
    <p>
        <button>计算button>
    p>
    <p>
        <h2>最大值:<span id="spanmax">span>h2>
        <h2>最小值:<span id="spanmin">span>h2>
    p>
function getValues() {  //获取input框value值,并放入数组之中;
    const numbers = [];
    const inps = document.querySelectorAll("input")
    for (let i = 0; i < inps.length; i++) {
        numbers.push(+inps[i].value)
    }
    return numbers;
}
const btn = document.querySelector("button")
btn.onclick = function () {  //点击按钮计算input框中value值的大小
    const numbers = getValues(); //得到文本框中的所有数字形成的数组
    spanmax.innerText = Math.max(...numbers)
    spanmin.innerText = Math.min(...numbers)
}

4-4.3

 // curry: 柯里化  用与固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接受剩余的参数;
        function cal(a,b,c,d){  
            return  a + b * c + d;
        }
        
        function curry(func,...args){   //柯里化函数
            return function(...args1){
                const newArr = [...args,...args1];
                if(newArr.length >= func.length){
                    // 参数够了
                   return  func(...newArr);
                }else{
                    //参数不够,继续固定
                    return curry(func,...newArr);
                }
            }
        }
        
        const newCal = curry(cal,2,3);

        console.log(newCal(4,5));  // 2+3*4+5  19
        console.log(newCal(5,5));  // 2+3*5+5  22
        console.log(newCal(6,5));  // 2+3*6+5  25
        console.log(newCal(7,5));  // 2+3*7+5  28
        const newCal1 = newCal(8);
        console.log(newCal1(5));   // 2+3*8+5  31
4-5.明确函数的双重用途

ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用;

new.target
该表达式,得到的是:如果没有使用new来调用函数,则返回undefined ,如果使用new调用函数,则得到的是new关键字后面的函数本身。

function Person(firstName, lastName) {
    //判断是否是使用new的方式来调用的函数
    
    // //过去的判断方式
    // if (!(this instanceof Person)) {
    //     throw new Error("该函数没有使用new来调用")
    // }

    if (new.target === undefined) { //调用者不用new关键字调用则会抛出错误
        throw new Error("该函数没有使用new来调用")
    }
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = `${firstName} ${lastName}`;
}

const p1 = new Person("胖", "虎");
console.log(p1)  //正常执行打印

const p2 = Person("胖", "虎");
console.log(p2);  //会抛出错误

const p3 = Person.call(p1, "胖", "虎")  
console.log(p3);  //会抛出错误
4-6.箭头函数

回顾:this指向

  1. 通过对象调用函数,this指向对象
  2. 直接调用函数,this指向全局对象
  3. 如果通过new调用函数,this指向新创建的对象
  4. 如果通过apply、call、bind调用函数,this指向指定的数据
  5. 如果是DOM事件函数,this指向事件源
使用语法

箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数;

完整语法:

(参数1, 参数2, ...)=>{
    //函数体
}

如果参数只有一个,可以省略小括号

参数 => {

}

如果箭头函数只有一条返回语句,可以省略大括号,和return关键字

参数 => 返回值
注意细节
  • 箭头函数中,不存在this、arguments、new.target,如果使用了,则使用的是函数外层的对应的this、arguments、new.target
  • 箭头函数没有原型
  • 箭头函数不能作用构造函数使用
应用场景
  1. 临时性使用的函数,并不会可以调用它,比如:
    1. 事件处理函数
    2. 异步处理函数
    3. 其他临时性的函数
  2. 为了绑定外层this的函数
  3. 在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
案例:
const obj = {
    count: 0,
    start: function () {
        setInterval(() => {
            this.count++;
            console.log(this.count);
        }, 1000)
    },
    regEvent: function () {
        window.onclick = () => {
            console.log(this.count);
        }
    },
    print: function () {
        console.log(this)        //指向obj
        console.log(this.count) // 输出count的值;
    }
}
// obj.start();     //每隔一秒输出count的值;
// obj.regEvent();  //当点击的时候输出count的值;
obj.print();
//判断一个数是不是奇数

// const isOdd = function (num) {
//     return num % 2 !== 0;
// }

// const isOdd = (num) => {
//     return num % 2 !== 0;
// }

const isOdd = num => num % 2 !== 0;

console.log(isOdd(3))
console.log(isOdd(4))
const sum = (a, b) => ({
    a: a,
    b: b,
    sum: a + b
});
console.log(sum(3, 5));  //	{a: 3, b: 5, sum: 8}	
const obj = {
    method: function(){
        const func = () => {
            console.log(this)        //obj
            console.log(arguments)   // 234
        } 
        func()
    }
}
obj.method(234);
const numbers = [3, 7, 78, 3, 5, 345];

const result = numbers.filter(num => num % 2 !== 0)
   					  .map(num => num * 2)
    				  .reduce((a, b) => a + b)

console.log(result);  // 先按照条件过滤(保留奇数),然后映射(将过滤后的数组*2),最后求和, 726

你可能感兴趣的:(笔记)