图片 2

ES6所改良的javascript“缺陷”问题_javascript技巧_脚本之家

在ES6很多很棒的新特性中, 箭头函数
(或者大箭头函数)就是其中值得关注的一个! 它不仅仅是很棒很酷,
它很好的利用了作用域, 快捷方便的在现在使用以前我们用的技术,
减少了很多代码……但是如果你不了解箭头函数原理的话可能就有点难以理解.
所以,让我们来看下箭头函数, 就是现在!

块级作用域

图片 1

执行环境

你可以自己去学习和尝试下,
你可以简单的把示例程序代码复制到你的浏览器控制台下. 现在,
推荐使用Firefox(22+)开发者工具,
Firefox(22+)开发者工具现在支持箭头函数,你也可以使用谷歌浏览器.
如果你使用谷歌浏览器, 你必须要做下列两件事: – –
在谷歌浏览器中地址栏中输入:”about:flags”, 找到
“使用体验性Javascript”选项,开启使用。 – – 在函数的开头加上”use
strict”,然后再在你的谷歌浏览中测试箭头函数吧(提示:请用谷歌浏览器v38,我当时就是被浏览器版本坑了):

(function(){
    "use strict";
    // use arrow functions here
}());

幸运的是后面会有越来越多的浏览器支持ES6特性. 现在你完成了所有准备工作,
让我们继续深入它吧!

ES5没有块级作用域,只有全局作用域和函数作用域,由于这一点,变量的作用域甚广,所以一进入函数就要马上将它创建出来。这就造成了所谓的变量提升。

新手在入门 JavaScript 的过程中,一定会踩很多关于 this
的坑,出现问题的本质就是 this
指针的指向和自己想的不一样。笔者在入门学习的过程中,也踩了很多坑,于是便写下本篇文章记录自己“踩坑”历程。

一个新话题

最近大家在讨论关于ES6的一个话题:关于箭头函数, 像这样:

=>

ES5的“变量提升”这一特性往往一不小心就会造成一下错误:

一. this 在哪里?

在上篇《从 JavaScript 作用域说开去》分析中,我们知道,在 Execution
Context 中有一个属性是 this,这里的 this 就是我们所说的 this 。this
与上下文中可执行代码的类型有直接关系,** this
的值在进入执行上下文时确定,并且在执行上下文运行期间永久不变。**

this 到底取何值?this
的取值是动态的,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。因为
this
的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境。

所以 this
的作用就是用来指明执行上下文是在哪个上下文中被触发的对象。令人迷惑的地方就在这里,同一个函数,当在不同的上下文进行调用的时候,this
的值就可能会不同。也就是说,this
的值就是函数调用表达式(也就是函数被调用的方式)的 caller。

新的语法

随着讨论产生了一个新的语法:

param => expression

新增的语法是作用在变量上, 可以在表达式中申明多个变量,
下面是箭头函数的使用模式:

//  一个参数对应一个表达式
param => expression;// 例如 x => x+2;

// 多个参数对应一个表达式
(param [, param]) => expression; //例如 (x,y) => (x + y);

// 一个参数对应多个表示式
param => {statements;} //例如 x = > { x++; return x;};

//  多个参数对应多个表达式
([param] [, param]) => {statements} // 例如 (x,y) => { x++;y++;return x*y;};

//表达式里没有参数
() => expression; //例如var flag = (() => 2)(); flag等于2

() => {statements;} //例如 var flag = (() => {return 1;})(); flag就等于1

 //传入一个表达式,返回一个对象
([param]) => ({ key: value });
//例如  var fuc = (x) => ({key:x})
        var object = fuc(1);
        alert(object);//{key:1}

1.内层变量覆盖外层变量

二. this & that 具体值得是谁?

目前接触的有以下14种情况,笔者打算一一列举出来,以后如果遇到了更多的情况,还会继续增加。

既然 this
是执行上下文确定的,那么从执行上下文的种类进行分类,可以分为3种:

图片 2

那么接下来我们就从 Global Execution Context 全局执行上下文,Function
Execution Context 函数执行上下文,Eval Execution Context Eval执行上下文
这三类,具体谈谈 this 究竟指的是谁。

这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象 Global。

var name = 'halfrost';function test() { console.log; // window console.log(this.name); // halfrost} test();

在全局上下文(Global Context)中,this 总是 global
object,在浏览器中就是 window 对象。

严格模式由 ECMAScript 5.1 引进,用来限制 JavaScript
的一些异常处理,提供更好的安全性和更强壮的错误检查机制。使用严格模式,只需要将
‘use strict’ 置于函数体的顶部。这样就可以将上下文环境中的 this 转为
undefined。这样执行上下文环境不再是全局对象,与非严格模式刚好相反。

在严格模式下,情况并不是仅仅是 undefined
这么简单,有可能严格模式夹杂着非严格模式。

先看严格模式的情况:

'use strict';function test() { console.log; //undefined};test();

上面的这个情况比较好理解,还有一种情况也是严格模式下的:

 function execute() { 'use strict'; // 开启严格模式 function test() { // 内部函数也是严格模式 console.log; // undefined } // 在严格模式下调用 test() // this 在 test() 下是 undefined test(); // undefined } execute(); 

如果严格模式在外层,那么在执行作用域内部声明的函数,它会继承严格模式。

接下来就看看严格模式和非严格模式混合的情况。

 function nonStrict() { // 非严格模式 console.log; // window } function strict() { 'use strict'; // 严格模式 console.log; // undefined }

这种情况就比较简单了,各个模式下分别判断就可以了。

当通过正常的方式调用一个函数的时候,this 的值就会被设置为 global
object(浏览器中的 window 对象)。

严格模式和非严格模式的情况和上述全局执行上下文的情况一致,严格模式对应的
undefined ,非严格模式对应的 window 这里就不再赘述了。

var person = { name: "halfrost", func: function () { console.log(this + ":" + this.name); }};person.func(); // halfrost

在这个例子里面的 this 调用的是函数的调用者 person,所以会输出
person.name 。

当然如果函数的调用者是一个全局对象的话,那么这里的 this
指向又会发生变化。

var name = "YDZ";var person = { name: "halfrost", func: function () { console.log(this + ":" + this.name); }};temp = person.func;temp(); // YDZ

在上面这个例子里面,由于函数被赋值到了另一个变量中,并没有作为 person
的一个属性被调用,那么 this 的值就是 window。

上述现象其实可以描述为,“从一个类中提取方式时丢失了 this
对象
”。针对这个现象可以再举一个例子:

var counter = { count: 0, inc: function() { this.count ++; }}var func = counter.inc;func();counter.count; // 输出0,会发现func函数根本不起作用

这里我们虽然把 counter.inc 函数提取出来了,但是函数里面的 this
变成了全局对象了,所以 func() 函数执行的结果是 window.count++。然而
window.count 根本不存在,且值是 undefined,对 undefined
操作,得到的结果只能是 NaN。

验证一下,我们打印全局的 count:

count // 输出是 NaN

那么这种情况我们应该如何解决呢?如果就是想提取出一个有用的方法给其他类使用呢?这个时候的正确做法是使用
bind 函数。

var func2 = counter.inc.bind;func2();counter.count; // 输出是1,函数生效了!

所谓构造函数就是用来 new 对象的函数。严格的来说,所有的函数都可以 new
一个对象,但是有些函数的定义是为了 new
一个对象,而有些函数则不是。另外注意,构造函数的函数名第一个字母大写。例如:Object、Array、Function等。

function person() { this.name = "halfrost"; this.age = 18; console.log;}var ydz = new person(); // person {name: "halfrost", age: 18}console.log(ydz.name, ydz.age); // halfrost 18

如果是构造函数被调用的话,this 其实指向的是 new 出来的那个对象。

如果不是被当做构造函数调用的话,情况有所区别:

function person() { this.name = "halfrost"; this.age = 18; console.log;}person(); // Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}

如果不是被当做构造函数调用的话,那就变成了普通函数调用的情况,那么这里的
this 就是 window。

构造函数里面如果还定义了 prototype,this 会指向什么呢?

function person() { this.name = "halfrost"; this.age = 18; console.log;}person.prototype.getName = function() { console.log(this.name); // person {name: "halfrost", age: 18} "halfrost"}var ydz = new person(); // person {name: "halfrost", age: 18}ydz.getName();

在 person.prototype.getName 函数中,this 指向的是 ydz 对象。因此可以通过
this.name 获取 ydz.name 的值。

其实,不仅仅是构造函数的 prototype,即便是在整个原型链中,this
代表的也都是当前对象的值。

如果在一个对象的属性是一个方法,这个方法里面又定义了内部函数和匿名函数,那么它们的
this 又是怎么样的呢?

var context = "global";var test = { context: "inside", method: function () { console.log(this + ":" +this.context); function f() { var context = "function"; console.log(this + ":" +this.context); }; f(); (function(){ var context = "function"; console.log(this + ":" +this.context); })(); }};test.method();// [object Object]:object// [object Window]:global// [object Window]:global

从输出可以看出,内部函数和匿名函数里面的 this 都是指向外面的 window。

this 本身是不可变的,但是 JavaScript 中提供了 call() / apply() / bind()
三个函数来在函数调用时设置 this 的值。

这三个函数的原型如下:

// Sets obj1 as the value of this inside fun() and calls fun() passing elements of argsArray as its arguments.fun.apply(obj1 [, argsArray])// Sets obj1 as the value of this inside fun() and calls fun() passing arg1, arg2, arg3, ... as its arguments.fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])// Returns the reference to the function fun with this inside fun() bound to obj1 and parameters of fun bound to the parameters specified arg1, arg2, arg3, ....fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])

在这3个函数里面,this 都是对应的第一个参数。

 var rabbit = { name: 'White Rabbit' }; function concatName { console.log(this === rabbit); // => true return string + this.name; } // 间接调用 concatName.call(rabbit, 'Hello '); // => 'Hello White Rabbit' concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit' 

apply() 和 call() 能够强制改变函数执行时的当前对象,让 this
指向其他对象。apply() 和 call() 的区别在于,apply()
的入参是一个数组,call() 的入参是一个参数列表。

apply() 和 call(),它俩都立即执行了函数,而 bind()
函数返回了一个新的函数,它允许创建预先设置好 this 的函数
,并可以延后调用。

 function multiply { 'use strict'; return this * number; } // 创建绑定函数,绑定上下文2 var double = multiply.bind; // 调用间接调用 double; // => 6 double; // => 20

bind()
函数实质其实是实现了,原始绑定函数共享相同的代码和作用域,但是在执行时拥有不同的上下文环境。

bind() 函数创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用
call() 或者
apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。

只有在构造器调用时,绑定函数可以改变上下文,然而这并不是特别推荐的做法。

 function getThis() { 'use strict'; return this; } var one = getThis.bind; // 绑定函数调用 one(); // => 1 // 使用 .apply() 和 .call() 绑定函数 one.call; // => 1 one.apply; // => 1 // 重新绑定 one.bind; // => 1 // 利用构造器方式调用绑定函数 new one(); // => Object 

只有 new one() 时可以改变绑定函数的上下文环境,其他类型的调用结果是 this
永远指向 1。

《 javascript 高级程序设计》中写到:“超时调用的代码需要调用 window
对象的 setTimeout 方法”。setTimeout/setInterval 执行的时候,this
默认指向 window 对象,除非手动改变 this 的指向。

var name = 'halfrost';function Person(){ this.name = 'YDZ'; this.sayName=function(){ console.log; // window console.log(this.name); // halfrost }; setTimeout(this.sayName, 10); }var person=new Person();

上面这个例子如果想改变 this 的指向,可是使用 apply/call 等,也可以使用
that 保存 this。

值得注意的是:setTimeout 中的回调函数在严格模式下也指向 window 而不是
undefined !

'use strict';function test() { console.log; //window}setTimeout;

因为 setTimeout 的回调函数如果没有指定的 this
,会做一个隐式的操作,将全局上下文注入进去,不管是在严格还是非严格模式下。

当一个函数被当作event handler的时候,this会被设置为触发事件的页面元素。

var body = document.getElementsByTagName[0];body.addEventListener("click", function(){ console.log;// <body>…</body>

当代码通过 in-line handler 执行的时候,this 同样指向拥有该handler
的页面元素。

看下面的代码:

document.write('<button onclick="console.log">Show this</button>');// <button onclick="console.log">Show this</button>document.write('<button onclick="(function(){console.log()">Show this</button>');// window

在第一行代码中,正如上面 in-line handler 所描述的,this 将指向 “button”
这个
element。但是,对于第二行代码中的匿名函数,是一个上下文无关(context-less)的函数,所以
this 会被默认的设置为window。

前面我们已经介绍过了 bind
函数,所以,通过下面的修改就能改变上面例子中第二行代码的行为:

document.write('<button onclick="((function(){console.log.bind">Show this</button>');// <button onclick="((function(){console.log.bind">Show this</button>

在 JavaScript
中,经常会存在嵌套函数,这是因为函数可以作为参数,并可以在合适的时候通过函数表达式创建。这会引发一些问题,如果一个方法包含一个普通函数,而你又想在后者的内部访问到前者,方法中的
this 会被普通函数的 this 覆盖,比如下面的例子:

var person = { name: 'halfrost', friends: [ 'AA', 'BB'], loop: function() { 'use strict'; this.friends.forEach( function { //  console.log(this.name + ' knows ' + friend); // ; }};

上述这个例子中,假设处的函数想要在这一行访问到 loop 方法里面的
this,该怎么做呢?

如果直接去调用 loop 方法是不行的,会发现报了下面这个错误。

person.loop();// Uncaught TypeError: Cannot read property 'name' of undefined

因为处的函数拥有自己的 this,是没有办法在里面调用外面一层的 this
的。那怎么办呢?

解决办法有3种:

that = this我们可以把外层的 this 保存一份,一般会使用 that
,self,me,这些变量名暂存 this。

var person = { name: 'halfrost', friends: [ 'AA', 'BB'], loop: function() { 'use strict'; var that = this; this.friends.forEach( function { //  console.log(that.name + ' knows ' + friend); // ; }};person.loop();// halfrost knows AA// halfrost knows BB

这样就可以正确的输出想要的答案了。

借助 bind() 函数,直接给回调函数的 this 绑定一个固定值,即函数的 this:

var person = { name: 'halfrost', friends: [ 'AA', 'BB'], loop: function() { 'use strict'; var that = this; this.friends.forEach( function { //  console.log(this.name + ' knows ' + friend); //  }.bind; }};person.loop();// halfrost knows AA// halfrost knows BB

forEach() 的 thisValue

这种方法特定于
forEach()中,因为在这个方法的回调函数里面提供了第二个参数,我们可以利用这个参数,让它来为我们提供
this:

var person = { name: 'halfrost', friends: [ 'AA', 'BB'], loop: function() { 'use strict'; var that = this; this.friends.forEach( function { //  console.log(this.name + ' knows ' + friend); //  }, this ); }};person.loop();// halfrost knows AA// halfrost knows BB

箭头函数是 ES6 增加的新用法。

 var numbers = [1, 2]; (function() { var get = () => { console.log(this === numbers); // => true return this; }; console.log(this === numbers); // => true get(); // => [1, 2] // 箭头函数使用 .apply() 和 .call() get.call; // => [1, 2] get.apply; // => [1, 2] // Bind get.bind; // => [1, 2] }).call;

从上面的例子可以看出:

  1. 箭头函数里面的 this
    对象就是定义时候所在的对象,而不是使用时所在的对象。

  2. 箭头函数不能被用来当做构造函数,于是也不能使用 new
    命令。否则会报错TypeError: get is not a constructor

    this 指向的固化,并不是因为箭头函数内部有绑定 this
    的机制,实际原因是箭头函数根本就没有自己的 this ,导致内部的 this
    就是外层代码块的 this。正因为它没有
    this,所以也就不能作为构造函数了。

  3. 箭头函数也不能使用 arguments 对象,因为 arguments
    对象在箭头函数体内不存在,如果要使用,可以用 rest
    参数代替。同样的,super,new.target
    在箭头函数里面也是不存在的。所以,arguments、super、new.target
    这3个变量在箭头函数里面都不存在。

  4. 箭头函数里面也不能使用 yield 命令,因此箭头函数也不能用作 Generator
    函数。

  5. 由于箭头函数没有自己的 this ,当然就不能用 call、bind()
    这些方法去改变 this 的指向。

虽然在 ES6 中引入了箭头函数可以绑定 this 对象,大大减少了显示绑定 this
对象的写法(call、apply、bind)。鉴于箭头函数有上述说到的4个缺点(不能当做构造函数,不能使用
arguments 对象,不能使用 yield 命令,不能使用call、apply、bind),所以在
ES7 中又提出了函数绑定运算符。用来取代 call、apply、bind 的调用。

函数绑定运算符是并排的双冒号,双冒号左边是一个对象,右边是一个函数。该运算符会自动的将左边的对象作为上下文环境绑定到右边的函数上。

foo::bar // 等同于 bar.bindfoo::bar(...arguments) // 等同于 bar.apply(foo,arguments)

Eval 函数比较特殊,this 指向就是当前作用域的对象。

var name = 'halfrost';var person = { name: 'YDZ', getName: function(){ eval("console.log(this.name)"); }}person.getName(); // YDZvar getName=person.getName;getName(); // halfrost

这里的结果和方法作为对象的属性被调用的结果是一样的。

箭头函数是怎么实现的

我们可以把一个普通函数转换成用箭头函数来实现:

// 当前函数
var func = function (param) {    
    return param.split(" ");
}
// 利用箭头函数实现
var func = param => param.split(" ");

从上面的例子中我们可以看出箭头函数的语法实际上是返回了一个新的函数,
这个函数有函数体和参数

因此, 我们可以这样调用刚才我们创建的函数:

func("Felipe Moura"); // returns ["Felipe", "Moura"]
var tmp = new Date {console.log { //执行则undefinedvar tmp = "hello world";}}

总结

如果要判断一个运行中函数的 this
绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断
this 的绑定对象。

  1. 函数是否在new中调用?如果是的话this绑定的是新创建的对象。var bar =
    new foo()
  2. 函数是否通过call、apply或者硬绑定调用?如果是的话,this绑定的是
    指定的对象。var bar = foo.call
  3. 函数是否在某个上下文对象中调用?如果是的话,this绑定的是那个上
    下文对象。var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到
    全局对象。var bar = foo()

ES6
中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定
this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this
绑定到什么)。这 其实和ES6之前代码中的self = this机制一样。

Reference:《ECMAScript 6 Primer》《javascript
高级程序设计》《JavaScript This 之谜》
原文

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source:

立即执行函数(IIFE)

你能在立即执行函数里使用箭头函数,例如:

( x => x * 2 )( 3 ); // 6

这行代码产生了一个临时函数,这个函数有一个形参x,函数的返回值为x*2,之后系统会马上执行这个临时函数,
3赋值给形参x.

下面的例子描述了临时函数体里有多行代码的情况:

( (x, y) => {
    x = x * 2;
    return x + y;
})( 3, "A" ); // "6A"

2.变量泄露,成为全局变量

相关思考

思考下面的函数:

var func = x => {
    return x++;
};

我们列出了一些常见的问题:

– 箭头函数创建的临时函数的arguments是我们预料的那样工作

console.log(arguments);

– typeofinstanceof函数也能正常检查临时函数

func instanceof Function; // true
typeof func; // function
func.constructor == Function; // true

– 把箭头函数放在括号内是无效的

//  有效的常规语法
(function (x, y){
    x= x * 2;
    return x + y;
} (3, "B") );

// 无效的箭头函数语法
( (x, y) => {
    x= x * 2;
    return x + y;
} ( 3, "A" ) );

// 但是可以这样写就是有效的了:
( (x,y) => {
    x= x * 2;return x + y;
} )( 3,"A" );//立即执行函数

– 尽管箭头函数会产生一个临时函数,但是这个临时函数不是一个构造函数

var instance= new func(); // TypeError: func is not a constructor

– 同样也没有原型对象

func.prototype; // undefined
var s = 'hello';for (var i = 0; i < s.length; i++) {console.log; // 5

作用域

这个箭头函数的作用域和其他函数有一些不同,如果不是严格模式,this关键字就是指向window,严格模式就是undefined,在构造函数里的this指向的是当前对象实例,如果this在一个对象的函数内则this指向的是这个对象,this有可能指向的是一个dom元素,例如当我们添加事件监听函数时,可能这个this的指向不是很直接,其实this(不止是this变量)变量的指向是根据一个规则来判断的:作用域流。下面我将演示this在事件监听函数和在对象函数内出现的情况:

在事件监听函数中:

document.body.addEventListener('click', function(evt){
    console.log(this); // the HTMLBodyElement itself
});

在构造函数里:

function Person () {

    let fullName = null;

    this.getName = function () {
        return fullName;
    };

    this.setName = function (name) {
        fullName = name;
        return this;
    };
}

let jon = new Person();
jon.setName("Jon Doe");
console.log(jon.getName()); // "Jon Doe"
//注:this关键字这里就不解释了,大家自己google,badu吧。

在这个例子中,如果我们让Person.setName函数返回Person对象本身,我们就可以这样用:

jon.setName("Jon Doe")
   .getName(); // "Jon Doe"

在一个对象里:

let obj = {
    foo: "bar",
    getIt: function () {
        return this.foo;
    }
};

console.log( obj.getIt() ); // "bar"

但是当执行流(比如使用了setTimeout)和作用域变了的时候,this也会变。

function Student(data){

    this.name = data.name || "Jon Doe";
    this.age = data.age>=0 ? data.age : -1;

    this.getInfo = function () {
        return this.name + ", " + this.age;
    };

    this.sayHi = function () {
        window.setTimeout( function () {
            console.log( this );
        }, 100 );
    }

}

let mary = new Student({
    name: "Mary Lou",
    age: 13
});

console.log( mary.getInfo() ); // "Mary Lou, 13"
mary.sayHi();
// window

setTimeout函数改变了执行流的情况时,this的指向会变成全局对象,或者是在严格模式下就是undefine,这样在setTimeout函数里面我们使用其他的变量去指向this对象,比如selfthat,当然不管你用什么变量,你首先应该在setTimeout访问之前,给selfthat赋值,或者使用bind方法不然这些变量就是undefined。

这是后就是箭头函数登场的时候了,它可以保持作用域,this的指向就不会变了。

让我们看下上文起先的例子,在这里我们使用箭头函数:

function Student(data){

    this.name = data.name || "Jon Doe";
    this.age = data.age>=0 ? data.age : -1;

    this.getInfo = function () {
        return this.name + ", " + this.age;
    };

    this.sayHi = function () {
        window.setTimeout( ()=>{ 
            // the only difference is here
            console.log( this );
        }, 100 );
    }

}

let mary = new Student({
    name: "Mary Lou",
    age: 13
});

console.log( mary.getInfo() ); // "Mary Lou, 13"
mary.sayHi();
// Object { name: "Mary Lou", age: 13, ... }

分析:在sayHi函数中,我们使用了箭头函数,当前作用域是在student对象的一个方法中,箭头函数生成的临时函数的作用域也就是student对象的sayHi函数的作用域。所以即使我们在setTimeout调用了箭头函数生成的临时函数,这个临时函数中的this也是正确的指向。

往常我们往往是使用闭包来解决这一问题的。现在,基于这一问题,ES6增加了块级作用域,所以不再需要自执行函数了。

有趣和有用的使用

创建一个函数很容易,我们可以利用它可以保持作用域的特征:

例如我们可以这么使用:Array.forEach()

var arr = ['a', 'e', 'i', 'o', 'u'];
arr.forEach(vowel => {
    console.log(vowel);
});

分析:在forEach里箭头函数会创建并返回一个临时函数
tempFun,这个tempFun你可以想象成这样的:function(vowel){
console.log(vowel);}但是Array.forEach函数会怎么去处理传入的tempFunc呢?在forEach函数里会这样调用它:tempFunc.call(this,value);所有我们看到函数的正确执行效果。

map里使用箭头函数,这里我就不分析函数执行过程了。。。。

var arr = ['a', 'e', 'i', 'o', 'u'];
arr.map(vowel => {
    return vowel.toUpperCase();
});
// [ "A", "E", "I", "O", "U" ]

费布拉奇数列

var factorial = (n) => {
    if(n==0) {
        return 1;
    }
    return (n * factorial (n-1) );
}

factorial(6); // 720

我们也可以用在Array.sort方法里:

let arr = ['a', 'e', 'i', 'o', 'u'];
arr.sort( (a, b)=> a < b? 1: -1 );

也可以在事件监听函数里使用:

document.body.addEventListener('click', event=>console.log(event, this)); // EventObject, BodyElement

let 和 const

ES6是是向后兼容的,而保持向后兼容性意味着永不改变JS代码在Web平台上的行为,所以var创建的变量其作用域依旧将会是全局作用域和函数作用域。这样以来,即使拥有了块级作用域,也无法解决ES5的“变量提升”问题。所以,这里ES6新增了俩个新关键词:let和const。

“let是更完美的var”,它有着更好的作用域规则。

const声明一个只读的常量。一旦声明,常量的值就不能改变,但const声明的对象可以有属性变化

const a = [];a.push; // 可执行a = ['Dave']; // 报错

也可以使用Object.freeze将对象冻结

const foo = Object.freeze;// 常规模式时,下面一行不起作用;// 严格模式时,该行会报错foo.prop = 123;//

使用let和const:

•变量只在声明所在的块级作用域内有效

•变量声明后方可使用

•不能重复定义变量

•声明的全局变量,不属于全局对象的属性

var a = 1;window.a // 1let b = 1;window.b // undefined

this关键字

我们知道,ES5函数中的this指向的是运行时所在的作用域。比如

function foo() {setTimeout{console.log;}, 100);}var id = 21;foo.call;//id: 21

在这里,我声明了一个函数foo,其内部为一个延迟函数setTimeout,每隔100ms打印一个this.id。我们通过foo.call来调用它,并且为这个函数设定作用域。它真正执行要等到100毫秒后,由于this指向的是运行时所在的作用域,所以这里的this就指向了全局对象window,而不是函数foo。这里:

•使用call来改变foo的执行上下文,使函数的执行上下文不再是window,从而来辨别setTimeout中的this指向

•setTimeout方法挂在window对象下,所以其this指向执行时所在的作用域——window对象。

超时调用的代码都是在全局作用域中执行的,因此函数中this
的值在非严格模式下指向window 对象,在严格模式下是undefined
–《javascript高级程序设计》

为了解决这一问题,我们往常的做法往往是将this赋值给其他变量:

function foo() {var that = this;setTimeout{console.log;}, 100);}var id = 21;foo.call;//id: 42

而现在ES6推出了箭头函数解决了这一问题。

箭头函数

var sum =  => { return num1 + num2; }// 等同于var sum = function {return num1 + num2;};

•如果函数只有一个参数,则可以省略圆括号

•如果函数只有一条返回语句,则可以省略大括号和return

•如果函数直接返回一个对象,必须在对象外面加上括号。(因为一个空对象{}和一个空的块
{} 看起来完全一样。所以需要用小括号包裹对象字面量。)

针对this关键字的问题,ES6规定箭头函数中的this绑定定义时所在的作用域,而不是指向运行时所在的作用域。这一以来,this指向固定化了,从而有利于封装回调函数。

function foo() {var that = this;setTimeout=>{console.log;}, 100);} var id = 21;foo.call;//id: 42

注意:箭头函数this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this。而箭头函数根本没有自己的this,其内部的this也就是外层代码块的this。这就导致了其:

•不能用作构造函数

•不能用call这些方法去改变this的指向

类与继承

传统ECMAScript没类的概念,它描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。而实现这一行为的传统方法便是通过构造函数:

function Point {this.x = x;this.y = y;}Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')';};var p = new Point;

在这里,构造函数Point会有一个原型对象,这个原型对象包含一个指向Point的指针,而实例p包含一个指向原型对象的内部指针。所以整个的继承是通过原型链来实现的。详情可见我的这篇文章:javascript中的prototype和constructor

class

ES6提供了更接近传统语言的写法,引入了Class这个概念,作为对象的模板。通过class关键字,可以定义类。但是类只是基于原型的面向对象模式的语法糖。对于class的引入,褒贬不一,很多人认为它反而是一大缺陷,但对我来说,这是一个好的语法糖,因为往常的原型链继承的方式往往能把我绕那么一会儿。

//定义类class Point {constructor {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}}var p = new Point;

•类里面有一个constructor方法,它是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

•constructor方法中的this关键字代表实例对象,

•定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

•使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致

•类的所有方法都定义在类的prototype属性上面

class的继承——extend

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class ColorPoint extends Point {constructor {super; // 调用父类的constructorthis.color = color;}toString() {return this.color + ' ' + super.toString}}

•super关键字,作为函数调用时,它代表父类的构造函数;作为对象调用时(即super.prop或super.method,它代表父类。在这里,它表示父类的构造函数,用来新建父类的this对象。

•子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

模块化

历史上,JavaScript一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来,这对开发大型的、复杂的项目形成了巨大障碍。为了适应大型模块的开发,社区制定了一些模块加载方案,比如CMD和AMD。

import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。

模块功能主要由两个命令构成:

•export

用于规定模块的对外接口,对外的接口,必须与模块内部的变量建立一一对应关系。

// 写法一export var m = 1;//错误export 1;// 写法二var m = 1;export {m};//错误export m;// 写法三 重命名var n = 1;export {n as m};

•import

用于输入其他模块提供的功能,它接受一个对象,里面指定要从其他模块导入的变量名

字符串插值

在javascript的开发中,我们常常需要这样来输出模板:

function sayHello{return "hello,my name is "+name+" I am "+getAge;}function getAge{return age;}sayHello //"hello,my name is brand I am 18"

我们需要使用+来连接字符串和变量。例子比较简单,所以看上去无伤大雅,但是一旦在比较复杂的情况下,就会显得相当繁琐不方便,这一用法也让我们不厌其烦。对此,ES6引入了模板字符串,可以方便优雅地将
JS 的值插入到字符串中。

模板字符串

•使用反引号“包裹;

•使用${}来输出值;

•${}里的内容可以是任何 JavaScript
表达式,所以函数调用和算数运算等都是合法的;

•如果一个值不是字符串,它将被转换为字符串;

•保留所有的空格、换行和缩进,并输出到结果字符串中

•内部使用反引号和大括号需要转义,转义使用反斜杠

对于上面的例子,模板字符串的写法是:

function sayHello{return `hello,my name is ${name} I am ${getAge}`;}function getAge{return age;}sayHello //"hello,my name is brandI am 18"

严格模式

严格模式的目标之一是允许更快地调试错误。帮助开发者调试的最佳途径是当确定的问题发生时抛出相应的错误(throw
errors when certain patterns
occur),而不是悄无声息地失败或者表现出奇怪的行为。严格模式下的代码会抛出更多的错误信息,能帮助开发者很快注意到一些必须立即解决的问题。在
ES5 中, 严格模式是可选项,但是在 ES6
中,许多特性要求必须使用严格模式,这个习惯有助于我们书写更好的
JavaScript。

以上所述是小编给大家介绍的ES6所改良的javascript“缺陷”问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

发表评论

电子邮件地址不会被公开。 必填项已用*标注