ECMAScript6(ES6)基础知识及核心原理
使用Babel编译ES6
一、下载安装Babel
环境:需要电脑上安装node(node中一般都会自带npm包管理器)
npm install babel-cli -g
把模块安装在全局环境下(在任何的项目中,都可以使用命令来编译我们的代码了)
npm uninstall babel-cli -g
把全局下安装的babel模块卸载掉
[图片上传失败...(image-be07d3-1570785377246)]
观看安装目录发现一些细节需要了解的知识点:
1、我们后期之所以可以使用babel的命令,是因为安装在全局环境下之后,会生成一些 xxx.cmd 的文件,而这里的xxx就是可以在DOC窗口中执行的命令
babel.cmd
以后可以使用babel命令了
babel-node.cmd
...2、执行babel命令后我们可以完成一些编译或者其它的任务,主要原因是执行babel命令后,会自动加载一些处理任务的文件
[图片上传失败...(image-b51a8a-1570785377246)]
二、配置.babelrc文件,安装一些语言解析包
1、我们需要把.babelrc文件配置在当前项目的根目录下(这个文件没有文件名,后缀名是babelrc)
a:在电脑上不能直接创建没有文件名的文件,我们需要使用WS中的 new -> file 来创建,或者使用命令创建
b:babelrc这个后缀名在某些ws中是不识别的,它其实是一个json文件,我们需要在ws中配置一下(让他隶属于json文件)
[图片上传失败...(image-34afb4-1570785377246)]
2、在文件中编写一些内容
{
"presets": [], //=>存放的是,我们编译代码时候需要依赖的语言解析包
"plugins": [] //=>存放的是,我们编译代码时候需要依赖的插件信息
}
3、安装依赖的语言解析包
在当前项目的根目录下安装(不是安装在全局),需要特殊注意的是:要在当前项目根目录中打开DOC命令才可以
npm install babel-preset-latest
安装最新已经发布的语言标准解析模块
npm install babel-preset-stage-2
安装当前还没有发布但是已经进入草案的语言解析模块(如果你的代码中用到了发布非标准的语法,我们需要安装他)
...
安装成功后在自己的当前项目根目录下,会存在一个node_modules
文件夹,在这个文件夹中有我们安装的模块4、完成最后.babelrc文件的配置
{
"presets": [
"latest",
"stage-2"
],
"plugins": []
}
三、使用命令编译JS代码
基本上所有支持命令操作的模块都有一个命令
babel --help / babel -h
查看帮助
babel --version / babel -V
查看版本号
babel --out-file / babel -o
把某一个JS文件中的ES6代码进行编译
babel --out-dir / babel -d
把某一个文件夹中所有的JS文件中的ES6代码进行编译
babel --watch / babel -w
监听文件中代码的改变,当代码改变后,会自动进行编译结束监听的程序:
ctrl+c 按两遍
[图片上传失败...(image-90e7f0-1570785377246)]
1.ES6
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"; 严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 禁止this指向全局对象
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface);
1.数组遍历方法
- forEach:遍历
- map:遍历,可以修改返回值
- find: 数组实例的
find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。find
方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。 - findIndex: 数组实例的
findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
。 - filter:筛选,返回一个新数组,原数组不变
- some::返回结果只要有一个true,就返回true
- every:返回结果只要有一个false,就返回false
上面都可以改this
- reduce:迭代,比如数组求和,consloe.log(ary.reduce((prev,item)=>{return prev+item}))
- reduceRight:从数组的最后一项往前迭代.
2.set &&map
- set可以数组去重,返回的是一个类数组.只有value没有key.遍历只能遍历他的value值,比如forEach((value,set)=>{})
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
//数组去重
let ary=[1,1,1,1,2,2,2];
console.log([...new Set(ary)]);//1,2
console.log(Object.is(NaN,NaN));解决NAN问题
Array.from方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
//这就提供了去除数组重复成员的另一种方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
keys方法、values方法、entries方法返回的都是遍历器对象,由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
这意味着,可以省略values方法,直接用for...of循环遍历 Set。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
set
add 增加一项,重复的加不上,返回当前set
set.add((value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。删除已有的返回true.
has(value):返回一个布尔值,表示该值是否为Set的成员。 返回true或者false
clear 清空没有返回值 undefined
size 相当于数组中的length.
map
它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
- 对象的属性名一定是字符串,如果写的不是字符串是其他类型,默认将其变为字符串.
- map的key可以是任意数据类型.
- map.set(key,value)返回值是这个MAP
- map.get(key);返回的是对应的value;
- map.has();返回的值是布尔类型的
- clear delete同set类似
- foreach key values entries
let map=new Map([[true,true],[{},{a:"a"}],[[],[]],[null,null]]);
//key可以是任意数据类型的
console.log(map);
//set(key,value) 返回值是这个 map
console.log(map.set(NaN, NaN));
//get(key)返回对应的value
console.log(map.get(true));
//has(key)->布尔值
console.log(map.has(NaN));//true
//clear delete
//foEach keys values entries
map.forEach((value,key,c)=>{});
for (let key of map.keys()){
//console.log(key);
}
for (let value of map.values()){
//console.log(value);
}
for (let [key,value] of map.entries()) {
console.log(value, key);
}
map.foreach((value,key,c)=>{
for(let key of map.keys()){consloe.log(key)};
for(let key of map.values()){consloe.log(value)};
for(let [key,value] of map.entries()){console.log(value,key)};
})
3.Symbol
- 基本数据类型 通过函数执行得到 不能使用new执行
- 唯一值
- 不能进行运算 因为不可以转数字 也不可以进行字符串拼接 这些都会报错
- 可以转为布尔值
- 当做属性名的时候只能用[""]的形式
- Object.getOwnPropertySymbols()
- Reflect.ownKeys方法可以返回所有类型的键名
4.proxy
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。
- set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
- has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
5.Iterator接口 和for of
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
next方法返回一个对象,表示当前数据成员的信息。这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再一次调用next方法。
总之,调用指针对象的next方法,就可以遍历事先给定的数据结构。
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内
调用 Iterator 接口的场合
- 解构赋值
- 扩展运算符
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
字符串是一个类似数组的对象,也原生具有 Iterator 接口。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
6.async await
- async 将一个函数变成promise对象
- await后面跟一个promise对象 如果不是默认将其变成一个resolve的promise 值作为resolve 的参数
- await的结果就是resolve或者reject的参数
- await异步执行完成之后再去执行后面的代码
7.Object.getOwnPropertyDescriptor
- configurable:true 是否可配置 是否可以删除这个属性
- enumerable:true 是否可枚举 是否可遍历
- value:"dali"
- writable:true 是否可修改
8.Object.defineProperty
let obj={name:"dali",age:10};
//new Proxy(target目标对象,{代理的方法})
let proxy1=new Proxy(obj,{
get(target,prop){
//获取属性名的属性值的时候就会触发这个get
return target[prop];
},
set(target,prop,value){
target[prop]=value;
}
});
Object.defineProperty(obj,"name",{
value:"mq",
enumerable:false,
writable:false,
configurable:false
});
ES6中的let和const
let基础语法
let 变量名 = 变量值
使用let创建变量和使用var创建变量的区别
1、let不存在变量提升机制
console.log(str);//=>undefined
console.log(fn);//=>FN本身
console.log(avg);//=>undefined
console.log(sum);//=>Uncaught ReferenceError: sum is not defined
console.log(num);//=>Uncaught ReferenceError: num is not defined
var str = '珠峰培训';
let num = 12;
function fn() {}
var avg = function () {};
let sum = function () {};
//=>ES6中只提供了创建变量的新语法标准(let),创建函数还是沿用ES5中的function(还会存在变量提升),如果想让函数也不存在变量提升,都使用函数表达式赋值的方式操作:let fn=function(){}
//=>创建变量
let xxx=xxx;
//=>创建函数
let xxx=function(){}
//=>自执行函数
;(function(){
})();
//=>好处:此时代码中就不要在考虑变量提升了,只要这样处理,没有所谓的变量提升
2、使用let定义的变量不允许在
同一个作用域中
重复声明
var num2 = 12;
var num2 = 13;
console.log(num2);//=>13
let str = '珠峰';
let str = '培训';
console.log(str);//=>Uncaught SyntaxError: Identifier 'str' has already been declared 当前报错,上面代码也不会执行(在JS代码执行之前就已经知道有重复声明的了,也就是浏览器依然存在类似于变量提升的机制:在JS代码之前先把所有LET声明的变量过一遍,发现有重复的直接报错)
let num = 12;
num = 13;
console.log(num);//=>13 LET不允许重复被声明,但是允许重新赋值
var att=200;
let att=100;//=>Uncaught SyntaxError: Identifier 'att' has already been declared 不管你之前使用什么方式在当前作用域中声明的变量,再使用let声明的时候都会报错
let num = 12,
fn = function () {
let num = 13;
};
console.log(num);//=>12 当前作用域下别重复声明即可(不同作用域中的变量是自己私有的,名字重复没有关系)
let att = 13,
sum = function () {
att = 14;
};
sum();
console.log(att);//=>let也存在私有变量和作用域链的概念,和ES5中基本上差不多 =>14
3、关于暂时性死区:使用typeof检测一个未被声明过的变量
ES5中返回的结果是undefined但是不报错
ES6中直接报错
"use strict";
console.log(typeof num);//=>undefined 当前变量不存在,但是使用typeof检测的时候,不会提示错误,而是返回undefined
console.log(typeof num);//=>Uncaught ReferenceError: num is not defined ES6中检测一个没有被声明过的变量直接报错,不像之前ES5中的值是UNDEFINED一样了
let num;
let num;
console.log(typeof num);//=>undefined 只声明没有定义(赋值),默认值是UNDEFINED
4、ES6语法创建的变量(let)存在块级作用域,ES5语法创建变量(var/function)没有块级作用域
[ES5]
window全局作用域
函数执行形成的私有作用域[ES6]
除了有ES5中的两个作用域,ES6中新增加块级作用域(我们可以把块级作用域理解为之前学习的私有作用域:存在私有变量和作用域链的一些机制)ES6语法中把大部分用大括号包起来都称之为块级作用域
let num = 12,
str = '';
let fn = function (str) {
str = 'HELLO';
//console.log(arguments[0]);//=>"HELLO" 当前JS并没有开启严格模式,所以形参变量和ARG存在映射机制(但是我们以后尽量不要这样处理:因为把ES6编译为ES5之后,会默认的开启严格模式,映射机制会中断,此处的值依然是'珠峰',这样导致我们的ES6结果和ES5结果不一致)
// console.log(num);//=>Uncaught ReferenceError: num is not defined
let num = 13;
console.log(num, str);//=>13 "HELLO"
};
fn('珠峰');
console.log(num, str);//=>12 ''
大部分我们遇到的大括号操作都是块级作用域
/*
if (10 >= 10) {
let total = 100;
}
console.log(total);//=>Uncaught ReferenceError: total is not defined 判断体也是一个块级私有作用域,在这个作用域中声明的变量是私有变量,在块级作用域之外是无法使用的
*/
/*
for (let i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);//=>Uncaught ReferenceError: i is not defined 循环体也是一个块级作用域(每一次循环都会形成一个新的块级作用域:当前案例形成五个块级作用域,每一个块级作用域中都有一个私有变量i,分别存储的是0~4)
*/
/*{
let i=12;
}
console.log(i);//=>Uncaught ReferenceError: i is not defined 这是一个ES6中标准的块级作用域*/
/*
let i=10;
{
let i=20;
{
{
console.log(i);//=>Uncaught ReferenceError: i is not defined 虽然ES6没有变量提升,但是每一次进入当前作用域都会把LET定义的变量找一遍(不提升但是找了,找到了说明当前作用域中是有这个变量的,提前用都会报错)
}
let i=30;
}
}
*/
/*
try{
let i=100;
}catch (e){
let k=200;
}
console.log(k);//=>Uncaught ReferenceError: k is not defined
console.log(i);//=>Uncaught ReferenceError: i is not defined TRY CATCH中的任何大括号都是块级作用域
*/
/*
switch (10){
case 10:
let i=20;
break;
}
console.log(i);//=>Uncaught ReferenceError: i is not defined
*/
/*
let obj={name:'珠峰'};
for (let key in obj) {
}
console.log(key);//=>Uncaught ReferenceError: key is not defined
*/
块级作用域的增加有什么用?
let tempList = document.getElementsByName('TEMP');
// for (var i = 0; i < tempList.length; i++) {
// tempList[i].onclick = function () {
// console.log(i);//=>5 怎么点击都是5 异步操作以及作用域链的查找,找到的都是全局下最后一次循环的结束值
// }
// }
//=>自定义属性解决
// for (var i = 0; i < tempList.length; i++) {
// tempList[i].index = i;
// tempList[i].onclick = function () {
// console.log(this.index);
// }
// }
//=>闭包解决
// for (var i = 0; i < tempList.length; i++) {
// ~function (i) {
// tempList[i].onclick = function () {
// console.log(i);
// }
// }(i);
// }
//=>使用ES6的块级作用域
for (let i = 0; i < tempList.length; i++) {
tempList[i].onclick = function () {
console.log(i);
}
}
const的基础语法
const的细节知识点和let一样,和let的主要区别在于:let是创建变量,const是创建常量
变量:值是可以修改的
常量:值不能被修改
let num = 12;
num = 13;
console.log(num);//=>13
const str = '珠峰';
str = '培训';//=>而且使用BABEL如果遇到了CONST设置的常量在进行修改,就无法进行编译了
console.log(str);//=>Uncaught TypeError: Assignment to constant variable.
JS中创建变量方式汇总
var
:ES5中创建变量
function
:ES5中创建函数
ES5中创建变量或者函数存在:变量提升、重复声明等特征,但是没有块级作用域的机制
let
:ES6中创建变量
const
:ES6创建常量
ES6中创建的变量或者常量都不可以变量提升,也不可以重复声明,而且还存在块级作用域
class
:ES6中创建类的方式
import
:ES6中模块导入的方式
class Parent{
constructor(){
//=>this.xxx=xxx
}
//=>Parent.prototype
aa(){}
//=>Parent own property
static bb(){}
}
ES6中的解构赋值
按照原有值的结构,把原有值中的某一部分内容快速获取到(快速赋值给一个变量)
数组的解构赋值
结构赋值本身是ES6的语法规范,使用什么关键字来声明这些变量是无所谓的
let [a,b,c] = [12,23,34];
//=>a:12 b:23 c:34
var [d,e,f] = [12,23,34];
//=>d:12 e:23 f:34
[g,h,i] = [12,23,34];
//=>此处相当于给window增加的全局属性
//g:12 h:23 i:34
//=>但是这个操作在JS的严格模式下是不允许的,因为严格模式下不允许出现非使用var/let等声明的变量
多维数组的结构赋值,可以让我们快速获取到需要的结果
let [,[,A],[,B,[,C]]] = [12, [23, 34], [45, 56, [67, 78]]];
console.log(A, B, C);//=>34 56 78
如果只想获取数组中前面的某几项内容,后面的结构不需要不全
let [D]=[12, 23, 34];
console.log(D);//=>12
let [,E]=[12, 23, 34];
console.log(E);//=>23
在解构赋值中,我们可以给某一项设置默认值
let [,,,A]=[12, 23, 34];
console.log(A);//=>undefined
let [,,,B = 0]=[12, 23, 34];
console.log(B);//=>0
在解构赋值中,支持
...xxx
的拓展运算符
let [A,...B]=[12, 23, 34, 45, 56];
console.log(A, B);//=>12 [23,34...]
let [...C]=[12, 23, 34, 45, 56];
console.log(C);//=>[12,23...] 数组克隆
let [D,...E,F]=[12, 23, 34, 45, 56];
console.log(D, E, F);//=>Uncaught SyntaxError: Rest element must be last element 拓展运算符只能出现在解构赋值中的结构末尾的位置
let [G,,,...H]=[12, 23, 34, 45, 56];
console.log(G, H);//=>12 [45,56]
对象的解构赋值
let {name, age}={name: '珠峰培训', age: 9};
console.log(name, age);//=>'珠峰培训' 9
let {A, B}={name: '珠峰培训', age: 9};
console.log(A, B);//=>在对象的解构赋值中需要注意的是:赋值的变量需要和对象中的属性名吻合,否则无法获取对应的属性值 undefined*2
let {C = 0}={name: '珠峰培训', age: 9};
console.log(C);//=>0 可以给当前的变量设置默认值
let {name}={name: '珠峰培训', age: 9};
console.log(name);//=>'珠峰培训' 和数组的解构赋值一样,我们可以把后面不需要获取的结构省略掉
let {,age}={name: '珠峰培训', age: 9};//=>Uncaught SyntaxError: Unexpected token , 和数组的解构赋值不一样的地方在于,对象前面不允许出现空来占位(因为对象获取需要通过具体的属性名获取,写成空的话,浏览器不知道怎么识别)
let {age}={name:'xxx',age:'xxx'};//=>但是我们可以把逗号去掉,这样就是只获取其中一个
let {name, ...arg}={name: '珠峰培训', age: 9, teacher: '周啸天'};
console.log(name, arg);//=>'珠峰培训' {age:9...} 支持拓展运算符的
//=>把对象进行浅克隆(只把第一级克隆了)
let obj = {name: 'xxx', age: 10, score: [100, 90, 80]};
let {...arg}=obj;
console.log(arg, obj);
console.log(arg === obj);//=>false
console.log(arg.score === obj.score);//=>true
let {name:A, age:B}={name: '珠峰培训', age: 9};
console.log(A, B);//=>'珠峰培训' 9 在对象的结构赋值中,我们可以把对象的属性名起一个小名(A和B相当于小名或者叫做别名)
let data = [
{
"name": "张三",
"age": 25,
"score": {
"english": [100, 90, 95],
"math": [100, 100, 100],
"chinese": [98, 99, 100]
}
},
{
"name": "李四",
"age": 24,
"score": {
"english": [8, 9, 3],
"math": [149, 150, 148],
"chinese": [138, 140, 145]
}
}
];
let [{score:{english:[A], math:[,B], chinese:[,,C]}}]=data;
console.log(A, B, C);//=>100 100 100
解构赋值的作用
快速交换两个变量的值
let a = 12;
let b = 13;
[a, b] = [b, a];
console.log(a, b);//=>13 12
接收函数返回的多个值
let fn = function () {
let a = 12,
b = 13,
c = 14;
return [a, b, c];
};
let [a,b,c] = fn();
console.log(a, b, c);//=>12 13 14
可以快速接收到传递的多个值(我传递的是一个数组或者对象)
let fn = function ([A,B,C,D = 0]) {
console.log(A, B, C, D);//=>12 23 34 0
};
fn([12, 23, 34]);
//console.log(A);//=>Uncaught ReferenceError: A is not defined 函数中的ABC是私有的变量
//=>快速处理options参数的初始化操作
let animate = function ({curEle = null, target = {}, duration = 1000, callBack = null}={}) {
console.log(curEle, target, duration, callBack);//=>body {opacity: 1} 1000 null
};
animate({
curEle: document.body,
target: {opacity: 1}
});
在ES6中支持给函数设置默认值
let fn = function (x) {
console.log(x);//=>undefined
x = x || 0;
console.log(x);//=>0
};
fn();
let fn2 = function (x = 0) {
console.log(x);//=>0
};
fn2();
按照一个数据值得结构,快速解析获取到其中的内容
1.真实项目中一般都是针对于数组或者对象进行解构赋值。
`数组解构赋值`
let ary=[1,2,3]
let[a,b,c]=ary;//让等号左边出现和右边相同的数据结构,左边可以创建一些变量快速获取到右侧对应位置的值
let[,,c]=ary;//=>c=3
获取第一项,把剩下的项作为一个数组返回
let[a,...b]=ary;//...在此处被称为剩余运算符
console.log(a,b);//=>1,[2,3]
let[a,...b,c]=ary;//会报错,剩余运算符只能处于解构中最后的位置
let ary=[12];
let [a,b=0]=ary;//解构的时候可以给变量设置默认值,如果当前变量对应结构中的这一项没有值,变量用默认值。
console.log(a,b);//12,0
如果对应的位置有值,会把默认的值覆盖。
let a=12,b=13;//让a,b互换位置
[a,b]=[b,a]
----------
对象解构赋值
let obj={name:"dali",age:25,high:184};
let {name,age}=obj;//左侧变量名和对象中的属性名保持一致才可以
let {high}=obj;//=>184;
let {age:ageAA}=obj;
console.log(ageAA);//=>25,给解构的属性名起别名作为我们使用的变量
let {friend=0}=obj;
console.log(friend);//=>0给不存在的属性设置默认值
let fn=function({}={}){//把传递的对象解构了,不传递值默认赋值为空对象:现在传递对象或者不传递,形参接收到的都是对象,结构的时候可以把传递进来的对象中,如果某个属性不存在我们赋值默认值
};
fn({name:"xxx",age:25})
let value={}
2."..."有三个含义:
1.剩余运算符
2.拓展运算符
3.展开运算符:把数组(对象/类数组)中的每一项展开,分别传递给一个函数
let ary=[1,2,3];
let[...arg]=ary;//=>ary.slice(0);
function fn(context,...arg)
//获取传递值中的第一个和剩下的{console.log(context,arg)//arg是一个数组、arguments是类数组。
}
let={}
fn(obj,1,2,3)
function sun(...arg){//类似于arguments但是arg是数组,arguments是类数组
}
let obj={name:"dali",age:25}
let newObj={...obj,sex:0}//{name:"dali",age:25,sex:0}
let ary=[12,23];
let Newary=[...ary,100,...ary];//=>[12,23,100,12,13];
ES6中的箭头函数
let fn=(x,y)=>{};
fn(10,20);
let fn=x=>{};只有一个形参我们可以省略小括号。
fn(10);
let fn=(x,y)=>x+y;
fn(10,20);//如果函数体只有一句操作并且是return的我们可以省略大括号(给形参设置默认值)
let fn=x=>y=>x+y;等同于
var fn=function fn(x){
return function(y){
return x+y;}}
箭头函数没有arguments,
let fn=(...arg)=>{};//可以使用剩余运算符而且arg是一个数组
fn(1,2,3,4);
箭头函数中没有自己的执行主体(this),他的this都是继承上下文中的this.
let obj={
fn:(function(){
return function (){console.log(this);}
})()
}
obj.fn();//this:obj//如何把this指向window,可以用call,也可以在自执行那里自定义一个变量,在return后的输出这个变量
用箭头函数:
let obj ={fn:(function(){return ()=>{console.log(this);}
})()
};
obj.fn();//this:window箭头函数执行和是否有点,点前面是谁都没有关系了,因为他没有自己的执行主体,在箭头函数中使用到的this都是直接找上下文中的this来使用
自执行函数用箭头函数表示:
(()=>{})();
箭头函数的基础语法
let fn = function (x, y) {
return x + y;
};
console.log(fn(10, 20));//=>30
//=>改写成箭头函数
let fn = (x, y)=> x + y;
let fn = function (n = 0) {
let x = 10,
y = 20;
return x + y + n;
};
//=>改写成箭头函数
let arrowFn = (n = 0)=> {
let x = 10,
y = 20;
return x + y + n;
};
箭头函数中不支持arguments
//=>传统函数支持ARGUMENTS
// let fn = function () {
// let arg = Array.prototype.slice.call(arguments);
// return eval(arg.join('+'));
// };
let fn = (...arg)=> {
//console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined
//=>不支持ARGUMENTS没事,我们使用ES6中的剩余运算符...来获取传递的进来的所有参数值(优势:使用剩余运算符接收到的结果本身就是一个数组,不需要再转换了)
//console.log(arg instanceof Array);//=>true
return eval(arg.join('+'));
};
//=>也可以把FN简写成以下方式
//let fn = (...arg)=> eval(arg.join('+'));
console.log(fn(10, 20, 30, 40));
箭头函数中的this问题
复习普通函数中this指向的问题
let obj = {
name: 'obj',
fn(){
//=>这样处理和下面SUM的处理是一样的
console.log(this);
},
sum: function () {
}
};
obj.fn();//=>this:obj 普通函数执行THIS的指向:看函数执行前面是否有点,有点,点前面是谁THIS就是谁,没有点THIS指向WINDOW或者UNDEFINED(严格模式下)
document.body.onclick = obj.fn;//=>this:body
setTimeout(obj.fn, 1000);//=>this:window
obj.fn.call(12);//=>this:12
箭头函数中没有自己的THIS指向,用到的THIS都是所在宿主环境(它的上级作用域)中的THIS
let obj = {
name: 'obj',
fn: ()=> {
console.log(this);
//=>不管我们怎么去操作,最后THIS都指向WINDOW:箭头函数中没有自己的THIS指向,用到的THIS都是所在宿主环境(它的上级作用域)中的THIS
}
};
obj.fn();
document.body.onclick = obj.fn;
setTimeout(obj.fn, 1000);
obj.fn.call(12);
以后实战项目中,不是要把所有的函数都改为箭头函数,根据自身需要来修改即可(例如:我们需要让函数中的this是宿主环境中的this,我们才来使用箭头函数;或者不涉及this问题,我们想让代码写起来简单一些也可以使用箭头函数;)
let obj = {
name: 'obj',
fn(){
//=>this:obj
// setTimeout(function () {
// //=>this:window
// }, 1000);
// setTimeout(function () {
// //=>this:obj
// }.bind(this), 1000);
// var _this = this;
// setTimeout(function () {
// //=>_this:obj
// }, 1000);
setTimeout(()=> {
//=>this:obj
}, 1000);
}
};
obj.fn();
箭头函数的一点扩充
宿主环境:不是执行的环境而是定义的环境
let fn = ()=> {
console.log(this);
};
let obj = {
name: 'obj',
sum: function () {
//=>this:obj
fn();//=>this:window
//宿主环境:不是执行的环境而是定义的环境,FN虽然是在这执行的,但是它是在WINDOW下定义的,所以它的宿主环境还是WINDOW
}
};
obj.sum();
层级嵌套的箭头函数
// let fn = function (i) {
// return function (n) {
// return n + (++i);
// }
// };
let fn = (i)=> (n)=> n + (++i);
ES6中的类和继承
ES5中创建类和实例,以及如何禁止用户把类当做普通函数执行:
new.target
function Person(name, age) {
//console.log(new.target);//=>ES6新增加的语法,如果是通过NEW执行的,返回的结果是当前创建的类,如果是当做普通函数执行的,返回的是UNDEFINED
if (typeof new.target === 'undefined') {
throw new SyntaxError(`当前Person不能作为一个普通函数执行,请使用new Person来执行~~`);
}
//=>NEW执行的时候,THIS是当前类的实例,THIS.XXX=XXX是给当前实例增加的私有属性
this.name = name;
this.age = age;
}
//=>原型上存放的是公有的属性和方法:给创建的实例使用
Person.prototype = {
constructor: Person,
say: function () {
console.log(`my name is ${this.name},i am ${this.age} years old~`);
}
};
//=>把PERSON当做一个普通的对象,给对象设置的私有属性
Person.study = function () {
console.log(`good good study,day day up~`);
};
var p1 = new Person('王雪超', '80');
Person('王雪超', '80');
ES6中创建类
//console.log(Person);//=>Uncaught ReferenceError: Person is not defined 不存在变量提升
class Person {
constructor(name = '珠峰培训', age = 9) {
//=>给实例设置的私有属性
this.name = name;
this.age = age;
}
//=>直接在大括号中编写的方法都设置在类的原型上:ES6默认把CONSTRUCTOR的问题解决了,此时原型上的CONSTRUCTOR指向的就是PERSON
say() {
console.log(`my name is ${this.name},i am ${this.age} years old~`);
}
//=>把PERSON当做普通对象设置属性和方法,只需要在设置的方法前面加STATIC即可
static study() {
console.log(`good good study,day day up~`);
}
}
let p1 = new Person('王雪超');
//Person();//=>Uncaught TypeError: Class constructor Person cannot be invoked without 'new' =>ES6中使用CLASS创建的类,天生自带NEW.TARGET的验证,不允许把创建的类当做普通函数执行
ES6中的继承
class Person {
constructor(...ARG) {
let [x = 0,y = 0]=ARG;
this.x = x;
this.y = y;
}
sum() {
return this.x + this.y;
}
}
class Child extends Person {
//=>创建CHILD类,并且让CHILD类继承了PERSON类:
//1、把PERSON中的私有属性继承过来设置给了子类实例的私有属性
//2、让子类实例的原型链上能够找到PERSON父类的原型(这样子类的实例就可以调用父类原型上的方法了)
//-------------
//=>我们可以不写CONSTRUCTOR,浏览器默认会创建它,而且默认就把父类私有的属性继承过来了(而且把传给子类的参数值也传递给父类了)
// constructor(...arg) {
// //=>ARG:传递给子类的参数(数组) [剩余运算符]
// super(...arg);//=>[展开运算符] 把ARG中每一项值展开,分别传递给父类方法 SUPPER(10,20,30)
// }
//------------
//=>很多时候我们不仅要继承父类私有的,还需要给子类增加一些而外私有的,此时就必须写CONSTRUCTOR,但是一定要在CONSTRUCTOR中的第一行写上SUPPER,否则会报错
// constructor(...arg) {
// super(...arg);
//
// let [,,z]=arg;
// this.z = z;
// }
constructor(x, y, z) {
super();//<=>Person.prototype.constructor.call(this)
this.z = z;
}
fn() {
}
}
let c = new Child(10, 20, 30);
ES6:let创建的变量不存在变量提升
在es6中基于let或者const等方式创建变量或者函数
- 不存在变量提升机制,
- 切断了全局变量和window属性的映射机制。
- 在相同作用域中,基于let不能声明相同名字的变量( 不管用什么方式在当前作用域下声明了变量再次使用let创建都会报错)
- 虽然没有变量提升机制,但是在当前作用于代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的直接抛出异常代码也不会在执行了(虽然没有把变量提前声明定义但是浏览器记住了当前作用域有哪些变量)
- 基于let和基于var创建变量,在私有变量和作用域链机制上是一样的
- 现有项目当中是es5和es6混合开发模式,如果当前变量是基于let创建的,就按照es6的语法机制渲染的,否则按照es5的老语法机制渲染
基于let创建的变量存储块级作用域(类似于函数执行形成的私有作用域=》一般来说基本上所有的{}都会形成块级作用域。
包括:let,循环体,判断体,{}。
console.log(a);//报错语法错误:a is not undefined
let a=12;
console.log(window.a);//undefined
let a = 10,
b = 20;
let fn = function () {
console.log(a, b);//a会报语法错误,not defined.没有变量提升
let a = b = 30;//let a=20,b=20
console.log(a, b);
};
console.log(a, b);
var a=12;
if(true){
consloe.log(a);//会报错a is not defined
let a=13;//基于let创建变量会把大部分{}当做一个私有的块级作用域(类似函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语法创建的变量,如果是按照新语法规范来解析
}
console.log(typeof a);//""undefined",在原有浏览器渲染机制下,基于typeof 等逻辑运算符检测一个未被声明的变量,不会报错返回undefined
console.log(typeof a);//报错,a is not undefined
let a=13;//如果当前变量是基于es6语法处理,在没有声明这个变量的时候使用typeof检测会直接报错不会是undefined,解决了原有的js死区。
全局变量和私有变量
var a=12,b=13,c=14;
function fn(a){//a是形参,所以a是私有变量
console.log(a,b,c);//12,undefined,14
var b=c=a=20;//var b=20;c=20;a=20
console.log(a,b,c);}//20,20,20
fn(a);
conslole.log(a,b,c);//12,13,20
var ary=[12,23];
function fn(ary){
console.log(ary);//[12,23]
ary[0]=100;
ary=[100];
ary[0]=0;
console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100,23]
在私有变量当中,只有当前以下两种是私有变量“
1.带var的
2.形参也是私有变量
- 剩下的都不是私有变量,需要基于作用域链向上查找
查找上级作用域
- 当前函数执行形成一个私有作用域a,a的上级作用域是谁,和他在哪执行没有关系,和他在哪创建定义的有关系,在哪创建的他的上级作用域就是谁
var a=12;
function fn(){
//arguments:实参集合;
//arguments.callee:函数本身fn;
//arguments.callee.caller:当前函数在哪执行的,caller就是谁(记录的是他执行的宿主环境),在全局下执行caller的结果是null
}