ES6学习笔记(上)

268 阅读8分钟

[TOC]

0.前言:

ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。

ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

0.1 Babel解码器

因为目前浏览器的JavaScript是es5版本,因此我们会使用解码器将es6的代码转换为es5的代码从而可以使其在低版本浏览器上使用,这也就说明我们可以使用es6进行编程并且不用再担心浏览器的适配问题了。

在这里我们可以使用Babel解码器来解决这一问题。

这里我们介绍一下Babel 的详细用法。

1.安装Babel转码器

在npm上引入

 npm i babel-cli -g
 或npm install babel-cli --global

2.创建package.json(包管理)

npm init -y(快速生成)
或npm init(按步骤初始化)

3.创建一个.babelrc的文件

并且加入以下代码:;

{
    "presets": ["es2015"],
    "plugins": []
}

5. 安装转换码规则和转码核心包依赖包

 npm i --save-dev babel-preset-es2015 babel-cli

从这时候开始我们就可以直接开始编译es6的代码啦,具体的操作为

# 转码结果输出到标准输出
npm babel example.js
# 转码结果写入一个文件
npm babel example.js -o compiled.js
# 整个目录转码
npm babel src -d lib
#生成source map文件
npm babel src -d lib -s

小tips:

我们也可以将我们想要的操作写在package.json中

"scripts": {
    "build": "babel src -d dist",
  }

然后直接通过 npm run build进行编译

0.2 webpack自动化编译es6

学习了上面的解析方式,我们可以解析单个的es6文件,但是当我们修改了es6文件呢?是不是还要再去执行解析命令。这就使我们的开发效率大打折扣。所以我们可以使用webpack来自动化实时解析es6文件

下面贴出步骤

1.安装依赖包

npm i -S-dev @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime babel-loader babel-plugin-transform-runtime babel-polyfill babel-runtime webpack@3.11.0

2.修改.babelrc文件的内容

 {
      "presets":["@babel/preset-env"],
      "plugins":["@babel/transform-runtime"]
 }

3.在package.json中设置以下内容

"scripts": {
    "watch": "webpack --watch"
  }

3.创建并且配置webpack.config.js

let path = require('path');
const glob = require('glob');
module.exports = {
   entry: {
     demo: './src/demo.js',
     demo2:'./src/demo2.js'
   },
   output: {
     path: path.resolve(__dirname, './dist'),
     filename: '[name].js'
   },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: 'babel-loader'
      }
    ]
  }
}

到这里我们就可以使用npm run watch来实时监听生成相应文件啦!

但是到这里我们又想到一个问题。既然我们可以实现实时监听,那么我们可以实时添加更改文件吗?

当然可以,接着往下看(下面的方法是编译根目录下的所有文件)

4.安装插件glob

 npm i glob -S-dev

5.再在webpack.config.js文件中引入这个包

const glob = require('glob');

6.接着在entry和output中做相应的处理

  entry: glob.sync('./src/*.js'),
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      }

这样就完成啦,我们可以开始正式的学习es6的相关知识了

1. let和const命令

  1. let

  2. const

  3. 顶层对象

  4. es6变量声明

1.1 let

用法与var相似,但是所有的let命令只在所在的代码块内生效

块级定义域:

在ES5中变量只有全局和局部两种作用域,而在ES6增加了块级作用域(代码块)。

let和var的不同点:

  1. let只在他所在的代码块内生效,有闭包作用
  2. let不存在变量位置的提升,而var具有变量位置的提升
  3. let不允许在相同作用域内生命重复变量,而var可以
  4. var可能会存在变量泄露

1.2 const

用来生命一个常量,一般情况下常量使用大写来表示,以表示和内存变量的区别。

conset定义的常量为只读。

对于单值变量(基本变量)来说不可以修改其值。但是引用类型(对象、数组等)的常量使可以修改其值的,但是不能修改指向内容的内存地址。

1.3 顶层对象

在浏览器中顶层对象是window,node.js的顶层是global对象

但是在es6中,let、const、class和var定义的全局变量,它的顶部对象不再是windows

1.4 es6声明变量的六种方式

  1. let 变量声明
  2. const 常量声明
  3. class 类声明
  4. import 变量导入
  5. function 函数声明

2.解构赋值

结构赋值相当于模式匹配,只要等号两边的模式相等,左边的变量就会被赋给对应的值。

2.1 数组的解构赋值

数组的解构赋值实际上就是对应下标赋值,对应不到的就是undefined。

let [a,b,c]=['apple','pea','banbab'];
console.log(a,b,c);//apple pea banbab

let[a1,[b1,c1],d1]=['a',['b','c']];
console.log(a1,b1,c1,d1);//a b c undefined

let [a2,b2,...other]=['a','b','c','d','e','f','g']
console.log(a2,b2,other);//a b (5) ["c", "d", "e", "f", "g"]

2.2 对象的解构赋值

对象的解构赋值是通过键的名称匹配的

let {a3,b3}={a3:'a',b3:'b'}
console.log(a3,b3);//a b
let { a1, b1 } = { a1: 'apple', c1:'hehehehehe', xx1: 'xxxxxx', b1: 'banana' };
console.log(a1,b1); //apple banana
let { a2, xx2:{ b2 } } = { a2: 'apple', xx2: { b2: 'boy'}, b2: 'banana' };
console.log(a2,b2); //apple boy

2.3 函数参数的解构赋值

函数参数的解构赋值其实也是一样的,本质都是“匹配模式”,只不过这里是形参与实参之间的匹配。

let fun = ([a, b], { o }) => [a, b, o];
let fruits = fun(['apple', 'banana'], {g: 'grap', o: 'orange'});
console.log(fruits); //["apple", "banana", "orange"]


let apple = {name : 'apple', price : 8, num : 5};
let getTotal = function ({price, num}) {
    return price * num;
}
console.log(getTotal(apple)) //40

2.4 字符串的解构赋值

字符串解构赋值跟数组类似,它匹配的是字符串的下标。例如:

let [one, two, ...rest] = 'abcdef';
console.log(one, two); //a b
console.log(rest); // [c, d, e, f]

3.字符串的扩展

3.1 Unicode编码

Unicode是一种编码方案,Unicode是为了解决传统的字符编码方案的局限而产生的,他为每种语言种的每个字符串设定了同统一的并且唯一的二进制编码,以满足跨平台、跨语言进行文本转换、处理的要求。Unicode共有三种实现:分别为utf-8、utf-16、utf-32 。

ES6 加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。

例如:

console.log('\u9ed1'); //黑
console.log('\u{20aa9}'); //𠪩

3.2 字符串新增的方法

1.String.fromCodePoint()

ES5 提供String.fromCharCode()方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符。

ES6 提供了String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。

console.log(String.fromCodePoint(0x9ed1)); //黑

2.字符串.codePointAt()

获取字符串中指定位置字符的Unicode编码

console.log('黑'.codePointAt(0).toString(16)); //9ed1

除了这两个以外还有以下方法:

at(): 识别 Unicode 编号大于0xFFFF的字符,返回正确的字符
normalize():将字符的多种表示方法统一为同样的形式,即 Unicode 标准化化
includes():返回布尔值,表示是否找到了参数字符串
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部
repeat():表示将字符串重复 n 次,返回字符串
padStart():字符串前补全长度
padEnd():字符串后补全长度
matchAll():方法返回一个正则表达式在当前字符串的所有匹配

3.3 字符串的遍历

字符串的遍历经常可以使用for-of来遍历,并且可以解决含有四个字节的字符出现错误。而传统的for循环、for-in循环都不能解决这个问题

let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}// 乱码�

for (let i of text) {
  console.log(i);
}
// "𠮷"

3.4 模板语法

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可 以用来定义多行字符串,或者在字符串中嵌入变量。

嵌入变量的方式${变量名}

大括号内可以引用js的函数或者直接进行运算

let tname = 'Xu Wei',
    age = 30,
    sex = 'man';
let tpl = `<div><span>name: ${tname}</span><span>age: ${age}</span><span>sex: ${sex}
</span></div>`
console.log(tpl);
//<div><span>name: Xu Wei</span><span>age: 30</span><span>sex: man</span></div>

4.数组扩展

4.1 扩展运算符

es6中引入扩展运算符(...),它可以将将一个数组转化为以逗号分隔的参数序列

实例:使用...将数组拆分为零散的参数序列

let ttname=[1,2,3,4,5,6]
console.log(...ttname);//1,2,3,4,5,6
console.log(ttname);//[1,2,3,4,5,6]

替代函数的apply方法

既然扩展运算符可以展开数组,所以我们不再需要apply方法,将数组转化为函数的参数

function f(x,y,z) {
    console.log(x,y,z);
}
var args=[1,2,3]
// es5写法
f.apply(null,args);
// es6写法
f(...args);

扩展运算符的应用:

  1. 合并数组
  2. 与解构赋值结合起来用于生成数组(但扩展运算符在此情况下只能放置于尾部)
  3. 将字符串转为数组

5.函数扩展

5.1 函数参数设置默认值

从es6开始我们允许为函数的参数设置默认值,即直接在参数定义的后面,这样做有以下好处我们可以简单做一个了解。

  1. 使代码变得简洁自然。
  2. 可以方便明确的知道哪些参数有默认值是可以省略的
  3. 有利于将来的代码优化,即使去掉这个参数也不会导致以前的代码无法运行。
  4. 参数变量是默认声明的,因此不能再使用let和const命令再次声明。
  5. 定义了默认值的参数应该是函数的尾参 数,这样就可以很容易的看出到底省略了哪些参数。如果尾部参数没有设置默认值那么是无法省略的。另外 也无法直接省略处在中间的有默认值的参数,除非显示传入undefined。

实例:

function namea(a,b=200) {
    console.log(a+b);
}
namea(100);//300
namea(100,300);//400

5.2 函数参数默认值与解构赋值结合

参数默认值可以与结构赋值的默认值结合起来使用

实例:

function budsomeFruits({price=5,num=50}) {
    console.log(price*num);
}
budsomeFruits({price:6,num:30})//180
budsomeFruits({price:6})//300

5.3 rest参数

rest参数和一个变量名搭配使用,生成一个数组,用于获取函数的多余参数,这样就不需要使用arguments对象了。

注意:rest参数必须是最后一个参数,否则就会报错。

这也不难理解,因为我们多于参数的长度是不定的,因此需要放在最后。

示例:我们把rest参数的值赋给我们的第一个参数

function setArray(array,...arg) {
    for (const key in arg) {
        array.push(arg[key])
    }
    return array
}
console.log(setArray([],1,2,3,4,5));

5.4 函数的name属性

es6新增的的函数的name属性,返回该函数的函数名。

将一个匿名函数赋值给一个变量,ES5的name属性会返回空字符创,ES6的name属性会返回实际的函数名称。

var f = function(){};
console.log(f.name);//"f"

将一个具名函数赋值给一个变量,ES5和ES6的name属性都会返回这个具名函数的名称

const f = function alertFun() {};
console.log(f.name); // "alertFun"

构造函数返回的函数实例,name属性值为anonymous。

console.log((new Function).name);  // "anonymous"

bind返回的函数,name属性值会加上bound前缀。

function foo() {};
var name1 = foo.bind({}).name;
console.log(name1);// "bound foo"
var name2 = (function(){}).bind({}).name;
console.log(name2); // "bound "

5.5 箭头函数:

es6中新增箭头函数来表示匿名函数。

var f = v => v;
// 相当于
var f = function (v) {
    return v
}

它的使用有以下几种情况需要注意:

1.当箭头函数不需要参数或者需要多个参数时,就是用一个圆括号代表参数部分

 var getname=(one,two)=>{return one*two}
 console.log(getname(2,2));

2.箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回

3.由于大括号被解释为代码块,所以如果箭头函数要返回一个对象的话必须要在外面加上一个⚪括号

var getTempItem = id => ({ id: id, name: "Temp" });

4.箭头函数需要与变量解构结合使用

const full = ({first,last}) => first + ' ' + last;
let tt={
    first:'zhang',
    last:'san',
    sex:'nv'
}
console.log(full(tt));

5.箭头函数中的this总是指向函数定义生效时所在的对象中

function test() {
    setTimeout(() => {
        console.log(this.id);
    }, 100);
}
var id=8888;
test.call({id:9999})//9999

本例中箭头函数生效在{id:9999}中。

我们在使用箭头函数时还有以下几个注意点需要我们留意:

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

6.对象的扩展

6.1 简洁表示法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

var foo='bar';
var baz={foo};
console.log(baz);

当键与值相同时,那么我么可以直接定义一个值就行。

除了属性可以简写,方法也可以简写。

var o = {
 method() {
 return "Hello!";
 }
};
/*等同于
var o = {
 method: function() {
 return "Hello!";
 }
};*/

6.2 对象合并

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

注意:如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

且对象合并出为浅克隆。

var obj1 = {name:"xiaohong",sex:"male"};
var obj2 = {name:"xiaoming",age:23};
var obj3 = Object.assign(obj1,obj2);
console.log(obj3);//{name:"xiaoming",sex:"male",age:23}

Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对 象拷贝得到的是这个对象的引用。对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而 不是添加。