ES6过渡史
ES:ECMAJavaScript的缩写
babel 转译器,将ES6–> ES5
2. traceur
块级作用域与嵌套、let、暂行性死区
- kiss原则:keep it simple,stupid
1. 块级作用域
2. let 关键字
声明的变量具有块级作用域,类似 { }
2.1 let声明的变量只在所处于的块级有效(可以防止循环变量变成全局变量)
if(true){
let a=10;
}
console.log(a);// a is not defined
2.2 不存在变量提升,因此在此之前不能使用let值,且在此之前的位置叫做暂时性死区(不能提升的这部分区域叫做暂时性死区)
console.log(a); // a is not defined
let a=20;
简单而又怪异的题目:
var a = a;
console.log(a); // undefined
let b = b; // b在没有赋值之前就引用了,而这是一个死区 (这个是自右向左执行的欸!把b赋值给b,而又因为b还没定义,所以报错)
console.log(b); // 报错
//===============================
function test(x = y,y = 2){
console.log(x,y); // 报错,因为这依然是个死区。它将y赋值给x,但是y还没有被定义
}
typeof有可能会报错了
console.log(typeof a); // 报错
let a;
ES6语法:能够在函数的参数进行赋值,给它默认值
2.3 暂时性死区
就是,在块级作用域中,使用let声明变量,这个变量会与这个块级整体进行绑定,不受外部影响。
var num=10;
if(true){
console.log(num); //会报错,num is not defined
let num=20;
}
2.4 在同一作用域下不能重复声明
(同一作用域:块级作用域、全局作用域、函数作用域)
例子:
function test(a){
let a = 10;
console.log(a); //报错了,形参处定义了a
}
test();
//===============================
function test(a){
{
let a = 10;
}
console.log(a); //undefined
}
test();
for(var i = 0; i < 10; i++){
i = 'a';
console.log(i);
}
打印出1个a,分析:
var i = 0;
for( i < 10; ){
i = 'a'; // 被重新赋值了
console.log(i); // 'a'
i++; //'a1'
}
//-----------------------------------------
for(var i = 0; i < 10; i++){
var i = 'a';
console.log(i);
}
打印出1个a,分析:
var i = 0;
for( i < 10; ){
var i = 'a'; // 被重新赋值了
console.log(i); // 'a'
i++; //'a1'
}
- let 用 for循环执行的时候有一个父子作用域的问题
”表达式也是一块作用域“
情况:
for(let i = 0; i < 10;i++){
i = 'a';
console.log(i); // 打印出1个a
}
分析:
{
let i = 0;
{
// ① 子级作用域可以使用父级作用域中的变量
// ② 因为子级里头没有嘛,所以他拿了父子的,并更改为'a'
// 再执行i++ 的时候就变为 'a1'
i = 'a';
}
}
情况:
for(let i = 0; i < 10; i++){
var i = 'a';
console.log(i); // 报错
}
分析:
{
var i; // 变量提升
let i = 0; // 相当于声明了两次,所以会报错
{
var i = 'a';
}
}
情况:
for(let i = 0; i < 10; i++){
let i = 'a';
console.log(i); // 打印出10个a
}
分析:
{
let i = 0; // 相当于声明了两次,所以会报错
{
let i = 'a';
}
}
打印 0-9 的情况:重新覆盖
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i);
}
//到最后 i = 10;
}
// 但因为此处重新声明了一次,所以i被覆盖了
for(var i = 0; i < 10; i++){
arr[i]();
}
区分:
函数声明只能在顶层作用域和函数作用域当中
函数声明提升是在当前作用域基础上的
const、全部变量与顶层对象
1.2 const 关键字
作用:声明常量。内存地址不能变化的量
(简单数据类型的值是不能变化的,遇上复杂数据类型时只可改里头的内容,“保证指针不变”)
- 具有块级作用域,也存在暂时性死区、不能提升、与let一样不能重复声明
- 声明常量时必需赋值
- 常量赋值后,值不可以修改(修改了会报错)
顶层对象
顶层对象——window
(不同的环境中顶层对象是会不一样的)
函数默认值、解构赋值、数组解构、对象解构
1. 函数默认值
对于ES5:
传入实参0,会被读成布尔值,这样就体现不了0了。因此这种方式在一些情况下就不合适
function foo(x,y){
x = x || 1;
y = y || 2;
console.log(x+y);
}
foo(); // 3
foo(5,6); // 11
foo(5); // 7
foo(null,6); // 7
foo(0,5); // 6
“参数可以赋值” (待完善)
参数可赋值,那就可以传变量,那参数的作用域是怎样的?(默认值作用域问题)
可以理解为它是 let声明的
惰性求值: 函数的参数为表达式的情况下,参数加载的方式是惰性求值的方式,每(执行)一次都要重新计算表达式的值
2. 解构赋值
-
解构失败:变量多了,没有“匹配”到的就是undefined
-
不完全解构: 值多了
解构赋值中依然可以给默认值,给值了就不会找默认值
2.1 数组解构
- 数组的解构存在顺序
关于数组解构例子:
let [a = 6] = [];
console.log(a); // 6
// 浏览器默认undefined为没有赋值
let [a,b = 6] = [1,undefined];
console.log(a,b); // 1, 6
let [a,b = 6] = [1,null];
console.log(a,b); // 1, null
let b = 5;
let [b = 6, c = b] = [];
console.log(b,c); // 报错,b已经被定义了
let [x = y, y = 1] = [];
console.log(x,y); // 报错,暂时性死区问题
2.2 对象解构
对象的属性还能拼接:
let firstName = 'Xing';
let secondName = 'is';
let name = 'Xing is';
let person = {
[firstName + secondName] : name
}
console.log(person);
对象的解构是不存在顺序的,是根据属性名来“匹配”的
▲:对象解构还需要捂一捂
隐式转换、函数参数解构、解构本质、()用法
()用法
解决:加一个括号,让大括号不再是作用域,而是表达式(如下)
let a;
({a} = {a:1};)
console.log(a);
- 用 let / var 声明,加括号就报错
(所以,定义参数的方式中间不能加括号)
数组也是特殊的对象,也能进行解构赋值
let arr = [1,2,4];
let {0: first, [arr.length - 1]:last} = arr;
console.log(first,last);
[(b)] = [3]; // 这样可以匹配成功
console.log(b); // 3
([b]) = [3];
console.log(b); // 不能成功,因为匹配的规则不一样,左边是表达式,右边是数组
({a: (b) = {}}); // 本身并没有匹配,这个是默认值
// 对象下面有个属性,属性默认值为{}
console.log(b); // {}
模式匹配:
首先要模式一样才能进行匹配
其次,有括号的时候一定要注意 这括号起的是什么作用
模式匹配,本质上是声明变量
函数传参也相当于一个变量的声明,声明的方式是通过let来声明的
函数参数解构
计算属性解构:
模式匹配可以匹配同源属性(同一个源属性)
var x = 200,
y = 300,
z = 100;
var obj1 = {
x: {
y: 42
},
z: {
y: z // 这个z是多少哇?
}
};
({y: x = {y: y}} = obj1); // obj1中没有y这个属性。所以就默认值
({z: y = {y: z}} = obj1); //100
({x: z = {y: x}} = obj1);
console.log(x.y, y.y, z.y);
function foo({x = 10} = {}, {y} = {y: 10}){
console.log(x, y);
}
foo(); // 10 10
foo({},{}); // 10 undefined;
foo({x: 2}, {y: 3}); // 2 3
注意:
({x = 10} = {}); ==>({x: x = 10} = {});
({y} = {y: 10}); ==> {y: y} = {y: 10}
解构隐式转换
转成类数组了:
const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e); // h,e,l,l,o
let {length : len} = 'hello'; // 转成类数组了,类数组具有length属性
console.log(len); // 5 解构出来了就表示隐式转换
数字 对应的包装类 Num
let {toString: s} = 123;
console.log(s === Number.prototype.toString); // true
布尔值 对应的包装类 Boolean
let {toString: s} = false;
console.log(s === Boolean.prototype.toString); // true
undefined 和 null 都没有隐式转换
let {prop} = undefined;
console.log(prop); // 报错
let {prop} = null;
console.log(prop); // 报错
这也是为什么调用函数时不传内容会报错,是因为空的内容并不能进行隐式转换
this指向、箭头函数基本形式、rest运算符
函数参数给了默认值的时候:
- 会让函数的length发生变化
变化依据:根据当前默认值的位置,只计算默认值前面的形参。
像这样:
function test(a, b, c = 1, d, e, f){
}
tset();
console.log(test.length); // 2
- 有arguments,但arguments的映射关系不存在了
回顾this指向
1. 默认绑定规则
- 普通函数指向的是window
- 立即执行函数的this 指向 window
- 定时器指向的是window
2. 隐式绑定:谁调用,指向谁
3. 显示绑定:call、apply、bind
4. new (优先级最高)
箭头函数(表达式)
格式:
( ) => { }
const fn = ( ) => { }
如何调用?
赋值给一个变量
- 如果形参只有一个,可以省略小括号
- 如果函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
- 箭头函数不绑定this关键字,this指向的是函数定义位置的上下文this
箭头函数可以和解构赋值结合来用
在箭头函数中不存在arguments (侧面说明,箭头函数不是用function来声明的)
(…)spread / rest运算符 (展开或收集)
1. spread 扩展运算符
作用:
- 可以将数组或者对象转为用逗号分隔的参数序列 (展开)
- 可以用于合并数组
let ary1 = [1,2,3];
let ary2 = [4,5,6];
let ary3 = [...ary1,...ary2];
//或 ary1.push(...ary2);
- 将 类(伪)数组或对象转换为真正的数组
let divs = document.getElementsByTagName('div');
divs=[...divs];
2. rest运算符——收集
(反问 = 返回)
箭头函数的实质、箭头函数的使用场景
1. 当箭头函数只执行一条,且想返回一个对象的时候(用括号把它包起来)
(a,b) => ({a:3, b:4})
不是用function来定义的,是用胖箭头来定义的
- this,根据函数定义位置的上下文this (外层函数)来决定的;
箭头函数的this 是不能用call的方式来指定的
this指向是固定化的,函数的内部并没有自己的this,只能通过父级作用域来获取到this,闭包的this
例1:
function fn(){
return (a) => {
console.log(this.a);
}
}
var obj1 = {a: 2};
var obj2 = {a: 3};
var bar = fn.call(obj1);
var bara = fn();
bar.call(obj2); // 2 (这样不会更改this指向,还是根据外层函数的this)
bar.call(obj2); // undefined
例2:▲!注意理解这个
const person = {
eat(){
console.log(this);
}
drink:() => {
console.log(this);
}
}
person.eat(); // person. this一开始是指向window的,在此处person调用eat时隐式的转换成person
person.drink(); // window. (对象先挂在全局,然后赋值给person,所以它的外层this是指向window)
新知:闭包
- 箭头函数不能作为构造函数来使用
- 在箭头函数中不存在arguments对象,所以使用 …(rest/spread)来代替
- yield 命令不能生效,在generator 函数中
箭头函数的使用场景
1: (这个需要先去回顾一下)
传统写法:
(function(){
// 构造函数
function Button(){
this.button = document.getElementById("button");
}
Button.prototype = {
init(){
this.bindEvent();
},
bindEvent(){
this.button.addEventListener('cilck',this.clickBtn.bind(this),false);
},
clickBtn(){
console.log(this);
}
}
new Button().init();
})();
使用箭头函数的写法:
(function(){
function Button(){
this.button = document.getElementById("button");
}
Button.prototype = {
init(){
this.bindEvent();
},
bindEvent(){
this.button.addEventListener('cilck',(e) => this.clickBtn(e),false);
},
clickBtn(e){
console.log(e);
console.log(this);
}
}
new Button().init();
})();
2.
在这里插入代码片
- 简单的函数表达式,得出唯一的return 计算值,并且函数内部没有this引用
递归、事件绑定、解绑定,用重构箭头函数的方式 - 内层函数表达式,需要调用this,确保this使用正确。通常会这么干:var self = this 或 bind(this); 这时候就可以使用箭头函数
4.