ES6历程
ECMA-262 ECMAscript 脚本语言规范:
- 1995 livescript -> JavaScript
- 1996 JavaScript 1.0/1.1
- 1997 JScript
- 1997.6 ECMAscript 1.0
- 1998.6 ECMAscript 2.0
- 1999.12 ECMAscript 3.0
- 2000 ECMAscript 4.0 草案没有通过(太过先进):TC39(technical committe 39)
- 2007 ECMAscript 4.0 准备发布又被推迟
- 2008.7 ECMAscript 3.1(在3.0的基础上进行小幅度升级)-> 更名ECMAscript5(大会项目代号hamony)
- 2009.12 正式发布ECMAscript5
- 内容还包括JavaScript.next(ES6) 和 JavaScript.next.next(ES7),都放入草案中
- 2011.6 ECMAscript5.1
- 2013.3 JavaScript.next(草案冻结)
- 2013.6 JavaScript.next(草案发布)
- 2015.6 ECMAscript6正式发布:从这开始每年6月份出来一个新版本升级
ES6的发布经历了十多年的时间,2015.6月份以来的所有版本(包括ECMAscript2016、ECMAscript2017、ECMAscript2019……)都是ES6的范畴。
let和块级作用域
var声明提升会存在很多问题,比如变量污染,在ES5中是靠用立即执行函数封闭作用域解决一部分问题的。
在ES6中,遵循kiss原则(keep it simple and stupid),产生了let语法和块级作用域。
let特征: 1. let不能在同一作用域下重复声明
var是可以的,只是会被覆盖
例1:
function test(a) {
let a = 10;
console.log(a);
}
test();
报错,因为在预编译的时候,a已经被定义过了,let不能重复定义。
例2:
function test(a) {
{
let a = 10;
}
console.log(a); //undefined
}
{}内部就是一个块级作用域,在外面无法调用
例3:
{
let a = 1;
function a(){}
}
这样也不行
例4:函数声明提升只会在当前的作用域提升,不会越出去的
{
let a = 1;
{
a();//10
function a(){
console.log(10);
}
}
console.log(a);//1
}
例5:
let x = 1;
function foo(y = x) {
let x = 2;
console.log(y);//1
}
foo()
参数赋值是在let x = 2前面的,所以拿不到2
例6:
let x = 1;
function foo(x = 2) {
let x = 2;
console.log(x);//报错
}
foo(5)
参数那已经定义过x了,类似于在函数的顶级作用域中已经用let声明过一次x了
例7:
let x = 1;
function foo(x = x) {
console.log(x);//报错
}
foo()
()内的区域和花括号内的区域是一个作用域
类似于:
let x = 1;
{
let x = x;
console.log(x);
}
子级的x没有被定义,不会拿父级的x = 1
特征:2. let不会提升,会产生一个暂时性死区(TDZ - temporal dead zone)
例1:
console.log(a);//报错,var会打印undefined不会报错
let a = 10;
例2:
var a = a;
console.log(a); //undefined
let x = x;
console.log(x); //报错 x is not defined
var a 会先提升,给a赋值为undefined,所以a = a =undefined
let不会提升,只能等它自右向左执行,将右边的x赋值给左边的x,但是这个时候x都还没有定义(let)他。在还没有定义的时候使用,就会报错
undefied 和 not defined 的区别,一个是没有赋值,一个是未被声明
例3:
function test(x = y, y = 2) {
console.log(x, y);//报错,y赋值给x的时候还没被定义
}
修改:
function test(x = 2, y = x) {
console.log(x, y);//2,2
}
typeof的问题
console.log(typeof a);//undefined
ES5不会因为没有声明变量的值报错,但是在ES6中typeof不再安全
console.log(typeof a);//报错
let a;
特征:3. let只能在当前的作用域下生效
例1:
function test(a) {
let a = 10;
}
test();
console.log(a);//报错
例2:
for(;1;) {
let a = 10;
}
console.log(a);//不报错
因为会一直死循环,就执行不到console……那行,所以不报错
在循环里加一个break就会报错了
例3:
for(let i = 0; i < 10; i++) {
}
console.log(i);//报错,let不会提升,不能提升到外面
for循环里,括号也是块级作用域
例4:
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i);
}
}
for(var i = 0; i < 10; i++) {
arr[i]();//打印0-9
}
上面的for循环执行完之后是有10个函数的数组,下面的for循环执行数组项,执行数组项的时候,i被覆盖,用的是下面的函数的i!
即使上面的循环执行完毕之后i是10,但是我们不用啊
用let转译之后:
var arr = [];
var _loop = function _loop(i) {
arr[i] = function() { //这里相当于有一个闭包,使用的i是传入的参数的i
console.log(i);
}
}
for(var i = 0; i < 10; i++) {
_loop(i); //每次循环都传入不一样的i,这样就不会有i都是10的问题了。
}
for(var i = 0; i < 10; i++) {
arr[i]();//打印0-9
}
然后让我们回到let本身:
var arr = [];
for(let i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i);
}
}
for(var i = 0; i < 10; i++) {
arr[i]();//打印0-9
}
这其实就相当于有闭包,子级作用域是不存在i的,那么就会去父级拿let缓存的i
{
let i = 0;
{
arr[i] = function() {
console.log(i);
}
}
}
也就是说子级作用域相当于一个封闭的函数作用域,i值以类似于参数的形式传进去,每一次循环传入的i值都是不一样的,打印的时候也能拿到传入的i。var就输在没有一个子级作用域,打印的时候只能拿到循环过后的i。
那么for循环里的括号和花括号都是块级作用域,是不是同一个作用域呢?
for(var i = 0; i < 10; i++) {
i = 'a';
console.log(i);//a
}
第一次循环i变成'a',第二次循环'a'进行隐式类型转换 —— NaN !< 10,所以循环只会进行一次。
for(let i = 0; i < 10; i++) {
i = 'a';
console.log(i);//a
}
for(let i = 0; i < 10; i++) {
var i = 'a';
console.log(i);//报错
}
看起来好像是在同一作用域下的,但其实只是因为var提升了,let不能重复定义
for(let i = 0; i < 10; i++) {
let i = 'a';
console.log(i);//10个a
}
但是经过这个例子可以看出不是一个作用域的。括号里的作用域其实是一个父作用域,两个作用域的i是不一样的,所以可以分别声明,有点类似下面的情况:
if(1) {
let a = 1;
{
//let a = 10;
console.log(a);//1
}
}
con……是可以拿到父级作用域的值的,并且因为不在同一个作用域中,可以用let重新声明a,把注释拿掉是可以打印10的
你把ES6转成ES5就可以看到
if(1) {
let a = 1;
{
let _a = 10;
console.log(_a);//10
}
}
这两个a根本不是一个东西
但是把子级的let改成var就会报错了,因为var是可以提升的,提到全局上,a就变成undefined了;到了if里面let又不能重新定义,所以会报错。
如果把子级作用域的let拿掉,因为没有let,相当于改值,a就是10
总结:let本质上就是为了给js增加一个块级作用域
ES6可以在块级作用域中嵌套函数
ES5中函数只能在顶层作用域和函数内部声明:
function test() {
function test1() {
//OK
}
}
{
function test1() {
//ES5不能在块级作用域里声明函数,ES6可以
}
}
但是不推荐这种方式,还是要用函数表达式的方式替代函数声明
try{
var test1 = function() {...}
}catch(e){
var test2 = function() {...}
}
块级作用域没有返回值
if(1){
return a;//没有对象来接收
}
可以用do的方式返回值,但是目前只是一个草案
虽然可以用函数立即执行的方式来模拟块级作用域,但是块级作用域和立即执行函数不是同一个东西。
const常量
常量是不可以被改变的
比如引入模块,不希望模块被更改,只是调用模块里的方法
const test = require('http');
特征:
1. 一旦定义必须赋值
定义常量之后必须要给他赋值,不赋值不就说明可变了吗,与常量的概念冲突,所以不赋值是会报错的。
2. 有块级作用域,不能提升,有暂时性死区
console.log(a); //报错
const a = 12;
3. 不能重复声明
Object.freeze()
如果保存的引用值,那么const只能保证栈指向堆的指针不变,不能保证引用值的内容不变。
const obj = {};
obj.name = 'jackson';
console.log(obj); //{name: 'jackson'}
如果要彻底保证对象不可变,就需要对对象进行冻结Object.freeze(obj)
const obj = {};
Object.freeze(obj);
obj.name = 'jackson';
console.log(obj); //{}
属性塞不进去了
那么如果对象里有属性也是一个对象的话,要怎么冻结呢?
function myFreeze(obj){
Object.freeze(obj);
for(var key in obj) {
if (typeof(obj[key] === 'object') && obj[key] !== null) {
Object.freeze(obj[key]);
}
}
}
但是这种方法很少用
一般会在源头上解决这个问题,也就是说模块最后是会返回一个实例化构造函数的。这样我们在引用模块的时候,不管怎么改,都不会影响模块(父级构造器,不要有引用值的问题)
顶层对象和全局变量
顶层对象的属性和全局变量是一样的,这其实会导致很多问题
比如说你声明并赋值了一个变量,后面想重新赋值的时候,不小心写错了,但是js是不会报错的,而是会挂到window下面的属性里
ES6就改变了这个状况
function、var允许声明的变量为全局变量
let、const、class不允许通过声明变量的方式声明全局变量的,不属于顶层对象的属性,默认生成一个块级作用域
顶层对象在不同的环境中是不一样的,例如在浏览器中是window,在node中是global
解构赋值
变量的解构,本质就是变量的赋值
前言
falsy虚值:经过boolean转换之后,值被认定为假的值
当下列函数传的是虚值的时候,会出现bug
function foo(x, y) {
x = x || 1;
y = y || 2;
console.log(x + y);
}
foo(0, 5); //6
所以要改成:
function foo(x, y) {
var a = typeof(arguments[0]) !== 'undefined' ? arguments[0] : 1;
var a = typeof(arguments[1]) !== 'undefined' ? arguments[1] : 2;
console.log(a + b);
}
在ES6中为了优化这种写法,可以这么写:
function foo(x = 1, y = 2) {
console.log(x + y);
}
例:
var w = 1, z = 2;
function foo(x = w + 1, y = x + 1, z = z + 1){
console.log(x, y, z);//报错
}
子级作用域可以拿到父级作用域是w值,所以x = 1 + 1;y表达式的x可以拿到前面的x,没有问题;但是z = z + 1,后面的z只会拿前面的z值,不会到外层去拿,我里面有值为什么要到外面拿,但是这样z是不会被成功定义的,所以报错。
参数传值,每次都是经过重新计算的。参数为表达式的情况下,加载方式是一种惰性求值的方式。
例:
let a = 99;
function foo(b = a + 1) {
console.log(b);
}
foo();//100
a = 100;
foo();//101
解构:其实也是一种赋值的过程,通过模式匹配(结构化赋值)简化赋值过程
数组解构
let [a, b, c] = [1, 2, 3];
let [d, [e], [f]] = [1, [2], [3]]; //只要结构一样就可以对应赋值
解构失败:变量多了,没有的值就用undefined来填充
let [d, [e], [f]] = [1, [], []];
console.log(d,e,f);//1,undefined,undefined
不完全解构:值多了
let [d, [], [f]] = [1, [2], [3]];
console.log(d,f);//1,3
解构赋值可以给默认值,除了undefined之外,都会用原本的值(js引擎会默认undefined是空,也就是没有填值)
let [a = 6] = [1];
console.log(a) //1
let [b = 8] = [];
console.log(b) //8
let [c, d = 2] = [1];
console.log(c, d); //1,2
let [e, f = 2] = [1, undefined];
console.log(e, f); //1,2
let [g, h = 2] = [1, null];
console.log(g, h); //1,null
解构的默认值可以是一个函数,但是函数默认的返回值是undefined。所以会先执行函数,打印10,然后返回undefined
function test(){
console.log(10);
}
let [x = test()] = []; // 10 undefined
也可用变量
let [x = 1, y = x] = [];
console.log(x, y); //1,1
let [a = 1, b = a] = [2];
console.log(a, b); //2,2
let [x = y, y = 1] = [];
console.log(x, y); //报错,y没被定义
对象解构
var name = 'jackson';
var age = 10;
var person = {
name: name,
age: age,
sex: 'male',
eat: functuin(){
console.log(1);
}
}
在ES6中如果对象的属性名和值一致,可以简写;对象方法也可简写
var name = 'jackson';
var age = 10;
var person = {
name,
age,
sex: 'male',
eat(){
console.log(1);
}
}
ES6的对象还可以属性拼接
let firstName = 'wang';
let secondName = 'jackson';
let name = 'wang jackson';
let person = {
[firstName + secondName] : name
}
console.log(person); //{wangjackson: "wang jackson"}
对象的解构
let {a: a, b: b, c: c} = {a: 1, b: 2, c: 3};
let {a, b, c} = {a: 1, b: 2, c: 3}; //简写
不完全解构 + 默认值
let {a = 2, b, c} = {b: 2, c: 3, e: 4, f: 5};
console.log(a, b, c); //2,2,3
解构失败
let {a = 2, b, c, d, e, f} = {b: 2, c: 3};
console.log(a, b, c, d, e, f); //2,2,3,undefined,undefined
数组的解构有顺序问题,对象没有
可以通过解构拿到JSON数据里的值
let[{"course": course1},{"course": course2}] = JSONData;
console.log(course1, course2);
var person = {
name: 'zhangsan',
age: 50,
son: {
name: 'lisi',
ageLi: 30,
son: {
name: 'wangwu',
ageWang: 12
}
}
}
怎样拿到最里面的儿子?
let {son: {son: son1}} = person;
console.log(son1);
如果属性名和变量一样的话,还可以简写
let {son: {son}} = person;
console.log(son);
一般我们还有这种写法
const {son} = person;
console.log(son);
这样我们就可以拿到person里面的整个son对象
如果要拿到李四和王五的年纪
let {son: {son:{ageWang}, ageLi}} = person;
console.log(ageLi,ageWang); //30,12
存在多层嵌套的问题,不要这么写
let {
son: {
son:{
ageWang
},
ageLi
}
} = person;
模式匹配
模式匹配还可以给对象的属性赋值
let a1 = [1, 2, 3], obj2 = {};
[obj2.a, obj2.b, obj2.c] = a1;
并且数组也是特殊的对象,也能够进行解构赋值
let arr = [1, 2, 3];
let {0: first, [arr.length - 1]: last} = arr;
console.log(first, last); //1,3
括号的问题
let {a} = {a: 1};
console.log(a);
如果想先声明a,像下面这样是不行的,js会把{}当成一个块
let a;
{a} = {a: 1};
添加括号变成表达式就可以了
let a;
({a} = {a: 1});
模式匹配本质上就是先声明一个变量,然后通过模式匹配的方式给变量赋值。
注意:用let/var声明,加了括号就报错(let没有被定义)
let ({a: b}) = {};
let {(a): b} = {};
[(b)] = [3];
console.log(b); //3
([b]) = [3];
console.log(b); //报错,模式不一样
({a: (b) = {}});
console.log(b); //{}
js认为(b) = {}是一个整体(默认值),所以这里本身并没有匹配,只是认为括号里有一个对象,属性a,默认值(b) = {}
例:如果要匹配属性的话,不能直接访问,而是要通过点语法访问
let obj1 = {a: 1, b: 2, c: 3}, obj2 = {};
({a: obj2.x, b: obj2.y, c: obj2.z} = obj1);
console.log(x, y, z);//报错
console.log(obj2.x, obj2.y, obj2.z); //1,2,3
例:设置属性和值
let a = 'x', b = 'y', obj = {};
({a: obj[a + b]} = {a: 2}); //要加括号
console.log(obj); //{'xy':2}
例:交换值
let a = 10, b = 20;
[b, a] = [a, b];
模式匹配可以匹配同源属性
let {a: x, a: y} = {a: 1};
console.log(x, y); //1,1
匹配的是同一个,而不是重复定义
重复定义是这样的:
let {a: x, a: x} = {a: 1};
例:
var x = 200, y = 300, z = 100;
var obj1 = {x: {y: 42}, z: {y: z}};
({y: x = {y: y}} = obj1); //obj1里面没有y,所以找默认值x = {y: y}
({z: y = {y: z}} = obj1);//匹配obj1里的z,z: {y: z}
({x: z = {y: x}} = obj1);//匹配obj1里的x,x: {y: 42}
console.log(x.y, y.y, z.y); //300,100,42
在解构中使用对象或数组,有了默认值代码的可读性就会下降,慎用!
体现在函数传参中
参数有没传完的地方就是undefined
function test([x, y]) {
console.log(x, y);
}
test([]); //undefined,undefined
什么都不传就会报错,无法进行隐式转换
function test({x, y}) {
console.log(x, y);
}
test({y: 2, x: 1}); //1,2
有默认值的情况:
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:x = 10} = {},属性是不可能会赋值的,所以它一定简写了
{y:y} = {y: 10}
给函数参数赋予默认值会影响函数的length属性
function test(a, b, c = 1){}
test(1);
console.log(test.length); //2
它是不会算有默认值的参数和之后的参数的。如果c = 1放在第一个,长度就为0。放中间也会截断形参的长度。
实参和形参是相互映射的关系,改一个另外一个会跟着改。
function test(a, b, c, d, e, f){
b = 7;
console.log(arguments[1]); //7
}
test(1, 2, 3, 4, 5, 6);
但是如果给了默认值,映射关系就不存在了!
例:兼容不传值的写法
function foo({x, y = 5}){
console.log(x,y);
}
foo({}); //undefined 5
foo({x: 1}); //1, 5
foo(); //报错
为了兼容最后一种写法,会给{x, y = 5}赋予一个默认值,如果传参为空,就赋予一个空对象
function foo({x, y = 5} = {}){
console.log(x,y);
}
foo(); //undefined 5
例:搜索
function fetch(url, {body = "", method = "GET", header = {}} = {}) { //属性简写了
console.log(method);
}
fetch('http://www.baidu.com')
用户一般只会输入网址,不会输入配置项,所以就需要给配置项一个默认值{}
例:
var x = 1;
function foo(x, y = function(){x = 2; console.log(x)}){
var x = 3;
y(); //2
console.log(x); //3
}
foo();
console.log(x); //1
在foo执行之前,全局对象上有x和foo
foo里面有两层作用域 foo:{local:{block}}
当foo执行到var x = 3之前。可以看到参数的x和执行体的x在不同的作用域里
y执行之前,块内的x赋值
y执行的前一刻,伴随着自己的作用域,还有foo的闭包(因为是在foo内部声明的,可以拿到形参的x)
y执行
在y中,x = 2
在foo中local环境中x = 2,block环境中,x依旧 = 3。所以x = 2这个赋值操作,并没有影响整个函数的 x = 3
所以说x = 2是让参数 = 2,外层和里层的作用域和x都不一样。
函数形成的时候,就定义了参数x和y,y函数里的x拿的是参数x。
如果把里层的var去掉,访问的就是上一层的local作用域,也就是将参数的x = 2改成3
var x = 1;
function foo(x, y = function(){x = 2; console.log(x)}){
x = 3;
y(); //2
console.log(x); //3
}
foo();
解构的隐式转换
const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e); //h e l l o
字符串转换为类数组,和数组一一对应
let {toString: s} = 233;
console.log(s);//f toString() {[native code]}
数字有对应的包装类,包装类是一个对象,Number的原型上有toString方法。
通过解构的方式,233进行了隐式转换
bool,number,string都可以进行隐式转换
null,undefined不可以
箭头函数表达式
基本形式:() => {}
特点:
- this指向根据外层函数的作用域决定的
- 不能作为构造函数来使用(没有this,所以也不能用call、apply、bind)
- 没有arguments对象,用rest(拓展运算符)替代
- yield命令不能生效,在generator函数中
let f = (a, b) => {
return a + b;
};
当参数只有一个的情况下,可以把()省掉,当表达式只有一个的情况下,可以把{}省掉
var f = function(a) {
return a;
}
等效于:
var f = a => a;
应用场景:
var arr = [54,85,32,855];
var arr1 = arr.sort((a, b) => a - b);
rest运算符
在箭头函数中不存在arguments!!!箭头函数并不是使用function来定义的,而是使用胖头‘=>’操作符来定义的,本质上是不一样的。
var sum = (a, b) => {
console.log(arguments);
return a + b;
}
sum(1, 2); //报错,arguments没有定义
那么替代方式就是.../rest/spread运算符:展开或收集
收集:参数 -> 数组
var sum = (...args) => { //args是变量名称,自定义
console.log(args); //打印出来是一个真真正正是数组[1,2],不是类数组
return args[0] + args[1];
}
sum(1, 2);
收集剩下的所有参数,只能放到最后
let fn = (a, b, ...c) => {
console.log(c); //[3,4,5,6]
}
fn(1, 2, 3, 4, 5, 6)
展开:数组 -> 参数
function foo (x, y, z) {
console.log(x, y, z); //1,2,3
}
foo(...[1, 2, 3]);
用ES5实现这个功能:
foo.apply(null, [1, 2, 3]);
合并数组,如果数组就要用concat方法
let a = [2, 3, 4];
let b = [1, ...a, 5];
console.log(b); //[1,2,3,4,5]
[1].concat(a, [5]);
arguments是类数组,要应用数组上的方法还要先转一下
function sortNum(){
Array.prototype.slice.call(arguments).sort(function(a, b){
return a - b;
})
}
console.log(sortNum(12,46,654,45,11,2));
...就不用,因为它返回的都是真正的数组
const sortNum = (...args) => args.sort((a, b) => a-b);
没有arguments,可以拿父级的arguments
function foo(){
setTimeout( () => {
console.log(arguments);//1,2,5,36,5
} );
}
foo(1,2,5,36,5)
rest和默认值一样,都不能通过length访问到长度,length就不再准确了。
console.log((function(b, c, d = 0, ..a){}).length); //2
拓展运算符在ES2017上又有了新的拓展,能够将对象拓展开
var obj = {a: 1};
var obj1 = {a: 2, b: 3};
var obj2 = {};
之前是用assign合并
Object.assign(obj2, obj, obj1);
现在可以直接用...拓展
var obj2 = {
...obj,
...obj1
};
箭头函数 + 解构赋值
const full = ({first, last} = {}) => first + '' + last;
console.log(full({first: 3, last: 5})); //35
使用场景
改写箭头函数:
function insert(value) {
return {
into: function(array) {
return {
after: function(afterValue) {
array.splice(array.indexOf(afterValue) + 1, 2, value);
return array;
}
}
}
}
}
console.log(insert(5).into([1,2,3,4,6,7,8]).after(4))
let insert = (value) => ({
into: (array) => ({
after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 2, value);
return array;
}
})
})
这样的语义化实际上不是很好的
使用场景:
- 简单的函数表达式,得出唯一的return计算值,函数的内部没有this引用:没有递归、事件绑定、解绑定,用重构 => 的方式
例如sort这样返回值比较单一的情况,用一行语句就可以完成
-
内层函数表达式,需要调用this,var self = this,bind(this),确保适当的this指向的时候
-
var args = Array.prototype.slice.call(arguments)
不适合箭头表达式的情况:函数声明,执行语句比较多的,还需要引用函数名,事件绑定、解绑定的情况,避免使用 =>
this
ES5的this指向:
- 默认绑定规则:函数内部this -> window,严格模式下this是undefined
- 隐式绑定:谁调用this就指向谁
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo: foo
}
obj.foo(); //obj
var bar = obj.foo(); //因为这个时候obj.foo()把指针转交给了bar,也就是bar调用的方法
bar();// 报错,bar是全局上的方法,全局里没有a,所以会报错
- 显式绑定:call、apply、bind
- new,this指向实例化对象(优先级最高)
箭头函数的this指向:根据外层函数的作用域决定的
function foo(){
return (a) => {
console.log(this.a);
}
}
var obj1 = {a: 2};
var obj2 = {a: 3};
var bar = foo.call(obj1);
bar.call(obj2); //2
根据外层函数,也就是foo的作用域来决定
const person = {
eat(){
console.log(this);
},
drink: () => {
console.log(this);
}
}
person.eat(); //person
person.drink(); //window,外层的作用域是全局
;(function(){
function Button(){
this.button = document.getElementById('button');
}
Button.prototype = {
init(){
thid.bindEvent();
},
bindEvent(){
//this.button.addEventListener('click', this.cliskBtn.bind(this), false);
this.button.addEventListener('click', (e) => this.cliskBtn(e), false);
},
cliskBtn(e){
console.log(e);
console.log(this); //Button构造函数
}
}
new Button().init();
})();
箭头函数的this指向就是Button实例化的时候确立的this指向
嵌套的时候的this指向
function foo(){
return () => {
return () => {
return () => {
console.log('id', this.id);
}
}
}
}
var f = foo.call({id: 1}); //1
var f1 = f.call({id: 2})()(); //call不成功,找父级,1
var f2 = f().call({id: 3})(); //1
var f3 = f()().call({id: 4}); //1
箭头函数上其实没有this的机制,在箭头函数中this的指向是固化的,内部的this就是外层的this,套那么多层都是外面的this指向。
箭头函数的内部并没有自己的this,只能通过父级作用域来获取this,闭包(foo)的this
一个函数的执行导致另一个函数的定义,就会形成闭包(执行的那个函数foo)
内部的函数bind、apply、call都适用,根本就没有this,怎么绑定