Bootstrap

前端知识--ES6

1、ES6新特性

JS 开发者必须知道的十个 ES6 新特性

1)ES6中的默认参数

/* ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。当传递参数的值为0时,因为0在JavaScript中算是false值,它会直接变成后面硬编码的值而不是0本身 */
function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}
log('Hello') // Hello World

/* ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面 */
function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World

2)ES6中的模版表达式

//传统的 JavaScript 语言,输出模板通常是这样写的
$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

//ES6写法:
/* 模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量 */
// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

//可以调用函数
const fn=()=>{ return '我是fn函数' }
let html = `我是模板字符串${fn()}`
console.log(html)  //  我是模板字符串  我是fn函数

3)ES6中的拆包表达式/解构赋值

/* ES6之前 */
var data = $('body').data(), // 假设data中有mouse和house的值
house = data.house,
mouse = data.mouse

/* ES6 */
var { house, mouse} = $('body').data() // 我们会拿到house和mouse的值的


//ES6交换两个变量的值
let a=5;
let b=3;
[a,b]=[b,a]

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
 
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'

4)ES6中改进的对象表达式

5)ES6中的箭头函数

6)ES6中的Promise

7)块级作用域的let和const

  1. let、const和var的差别

    1. var
      var声明的变量可进行声明提升,let和const不会
      var可以重复声明
      var在非函数作用域中定义是挂在到window上的
    2. let
      let声明的变量只在局部起作用,块作用域
      let防止变量污染,不可再声明
    3. const
      具有let的所有特征
      不可被改变
      如果使用const声明的是对象的话,是可以修改对象里面的值的
    //我们用{}来定义块,但是在ES5中这些花括号起不到任何作用
    function calculateTotalAmount (vip) {
      var amount = 0
      if (vip) {
        var amount = 1
      }
      { // 让块来的更疯狂
        var amount = 100
        {
          var amount = 1000
          //这里的变量定义并不在return层,但也能改变amount的值
          }
      }  
      return amount
    }
     
    console.log(calculateTotalAmount(true)) //输出1000
    console.log(amount) //报错   可见,通过var定义的变量不能跨函数作用域访问到
    
    //改成用let
    function calculateTotalAmount (vip) {
      var amount = 0 // 或许应该用let, 但你可以混用
      if (vip) {
        let amount = 1 // 第一个数量为 0
      }
      { // 更多的块
        let amount = 100 // 第一个数量为 0
        {
          let amount = 1000 // 第一个数量为 0
          }
      }  
      return amount
    }
     
    console.log(calculateTotalAmount(true)) //输出0
    
    var a = [];
    //变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
    /*
    那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
    */
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
    //如果for里使用var定义,则a[6]输出为10
    
  2. 声明提升

    函数内部会在开始前先定义需要使用的值,函数定义比变量早
    注意:这里指声明提升,是函数里有声明到变量或者函数才会这样,如果函数内部没有声明对应的变量就会往外层找
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    //内部没有声明变量,往外层找变量值
    var a = 10;
    function test() {
     console.log("22", a);//10    
    }
    test();
    console.log("11", a);//10
    
    //内部声明了变量
    var a = 10;
    function test() {
      console.log("22", a);  //undefined
      var a = 20;
      console.log("33", a);  //20
    }
    test();
    console.log("11", a);  //10	
    
    var flag = true;
    if (flag) {
      var a = "123";
    }
    function fn() {
      //在这个'b'前面没有var, 会往父层作用域找,一直往上找
      //找不到就报错
      //程序执行到这个就报错了,比下面的console.log更早
      b = 20;      
    }
    
    fn();
    console.log(a);
    //b这个变量在全局/局部都没有声明,所以console会报错
    console.log(b);
    

8)模块化

Module 的语法
JS ES6中export和import详解

  1. 概述
    1)历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。

    2)在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。

    3)ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。而ES6 模块不是对象。

    CommonJS只有运行时才能得到这个对象,从而获取到对象里的属性

    4)ES6 模块还有以下好处:

    1. 引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能
    2. 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
    3. 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
    4. 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
  2. 严格模式(严格模式是 ES5 引入的,不属于 ES6)
    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

    严格模式主要有以下限制。

    1. 变量必须声明后再使用
    2. 函数的参数不能有同名属性,否则报错
    3. 不能使用with语句
    4. 不能对只读属性赋值,否则报错
    5. 不能使用前缀 0 表示八进制数,否则报错
    6. 不能删除不可删除的属性,否则报错
    7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    8. eval不会在它的外层作用域引入变量
    9. eval和arguments不能被重新赋值
    10. arguments不会自动反映函数参数的变化
    11. 不能使用arguments.callee
    12. 不能使用arguments.caller
    13. 禁止this指向全局对象
    14. 不能使用fn.caller和fn.arguments获取函数调用的堆栈
    15. 增加了保留字(比如protected、static和interface)
  3. ES6的模块化无法在Node.js中执行,需要用Babel编辑成ES5后再执行

  4. Export:(变量/函数/class)

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};


// 报错
export 1;

// 报错
var m = 1;
export m;


// 报错
function f() {}
export f;

// 正确
export function f() {};

// 正确
function f() {}
export {f};

位置:export模块可以位于模块中的任何位置,但是必须是在模块顶层,如果在其他作用域内,会报错。

function foo() {
  export default 'bar' // SyntaxError
}
foo()
  1. Import
//写法1
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}


//import命令输入的变量都是只读的,因为它的本质是输入接口。
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;

//但是,如果a是一个对象,改写a的属性是允许的
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作




//写法2:别名
import { lastName as surname } from './profile.js';

//写法3:import语句会执行所加载的模块
//如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。
import 'lodash';
import 'lodash';


import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

1)注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。
上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

foo();

import { foo } from 'my_module';

2)由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

  1. 模块的整体加载
    1)除了指定加载某个输出值,还可以用(*)指定一个对象,所有的变量都会加载在这个对象上
import * as circle from './circle';

2)注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
  1. export default

    // 第一组
    export default function crc32() { // 输出
      // ...
    }
    
    import crc32 from 'crc32'; // 输入
    
    // 第二组
    export function crc32() { // 输出
      // ...
    };
    
    import {crc32} from 'crc32'; // 输入
    

    export default 和 export 区别:

    1. export与export default均可用于导出常量、函数、文件、模块等
    2. 在一个文件或模块中,export、import可以有多个,export default仅有一个
    3. 通过export方式导出,在导入时要加{ },export default则不需要
    4. 输出区别:
      1. 输出单个值,使用export default
      2. 输出多个值,使用export
      3. export default与普通的export不要同时使用
  2. 模块的继承:模块之间也可以继承

export * from 'circle'; //export *,表示再输出circle模块的所有属性和方法
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

//只输出circle模块的area方法,且将其改名为circleArea。
export { area as circleArea } from 'circle';

  1. import()函数:
    import和export命令只能在模块的顶层,不能在代码块之中。否则会语法报错

这样的设计,可以提高编译器效率,但是没有办法实现运行时加载

因为require是运行时加载,所以import命令没有办法代替require的动态加载功能。
所以引入了import()函数。完成动态加载

import()返回一个Promise对象, import()也可以用在 async 函数之中。

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

1)import()函数适用场合

//1.按需加载:
button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

//2.条件加载
if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

//3.动态的模块路径
//import()允许模块路径动态生成。下面代码中,根据函数f的返回结果,加载不同的模块。
import(f())
.then(...);
  1. JavaScript 模块的循环加载
//JavaScript 模块的循环加载
// a.js
var b = require('b');

// b.js
var a = require('a');
  1. 什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点

    1. 防止命名冲突
    2. 更好的分离,按需加载
    3. 更好的复用性
    4. 更高的维护性
  2. ES6 模块与 CommonJS 模块有三个重大差异。
    CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

9)(…)展开运算符

function test(...params){
	console.log(params)//[1, 2, 3, 4]
}
test(1,2,3,4)

let [arg1,arg2,...arg3] = [1, 2, 3, 4];
const [arg, ...arg1, arg2] = ["a", "b", "c", "d"]; //报错
//注意:结构赋值中展开运算符只能用在最后。	

10)字符串新增方法

  1. 确定一个字符串是否包含在另一个字符串中
    es5:indexOf
    es6:ncludes()

  2. repeat方法返回一个新字符串,表示将原字符串重复n次。

  3. 字符串补全长度的功能
    padStart()用于头部补全
    padEnd()用于尾部补全
    padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

  4. 消除字符串头部的空格、消除尾部的空格
    es5 :trim()
    es6:trimStart(),trimEnd()

11)set数据结构,set没有重复的值

  1. 数据去重
    let arr = [1,2,3,4,6,3,3,1,3,4];
    let removeRepeat = [...new Set(arr)];
    console.log(removeRepeat); //[1, 2, 3, 4, 6]
    
    其他数组去重:loadsh.cloneDeep,includes()

2、数据类型

  1. JS数据类型
    8种。Number、String、Boolean、Null、undefined、object、symbol、bigInt。

    null表示空对象
    undefined表示已在作用域中声明但未赋值的变量

  2. 基本类型
    String、Number、boolean、null、undefined。

  3. 引用类型:
    object:包含 function、Array、Date。RegExp,Error

基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,每个对象在堆中有一个引用地址。引用类型在栈中会保存他的引用地址,以便快速查找到堆内存中的对象。

顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗

;