目录

简介

ES6入门篇 - 由浅入深对比ES5

最近在学习前端,以前接触的都是ES5,所以趁这个机会来补一补ES6的知识,了解一些ES6的新特性,顺便记一笔,fighting!

常量

ES5 常量定义

Object.defineProperty(window, "PI2", {
    value: 3.1415926,
    writable: false, //不能写,即为只读
})
console.log(window.PI2);

ES6 常量定义

const PI = 3.1415926;
console.log(PI);

块作用域

ES5中作用域概念

const callbacks = [];
for (var i = 0; i <= 2; i++) {
    callbacks[i] = function() {
        return i * 2;
    }
}

console.table([
    callbacks[0](),
    callbacks[1](),
    callbacks[2](),
])

结果为:

ES6入门篇 - 由浅入深对比ES5

大家可能会疑惑为什么都是6呢?我起初也疑惑,原因是这样的:

最开始的var i = 0变量处在for循环外,第一个循环话callbacks[i]中的i取值为0,这都没问题,但

function() {
    return i * 2;
}

中的i是对变量的引用,而不是对i变量值的引用。函数体中是一个表达式,而不是一个值,所以这里有一个闭包。即callbacks[0]在执行的时候,其实i已经变成3了,也就是循环结束了,所以不管执行哪个,结果i的结果都为3。

再来看一个

const callbacks2 = [];
for (let j = 0; j <= 2; j++) {
    callbacks2[j] = function() {
        return j * 2;
    }
}

console.table([
    callbacks2[0](),
    callbacks2[1](),
    callbacks2[2](),
])

结果为:

ES6入门篇 - 由浅入深对比ES5

因为我们使用了let声明了变量j,let声明的变量有一个块作用域的概念,这个时候的闭包完全取决于当前的块作用域,块作用域就是for循环的花括号,即每循环一次生成一个新的作用域。

ES5中块作用域

块作用域,就是用块给代码做一个隔离,比如声明一个函数,只能在这块代码中运行。
我们在ES5中通常使用“立即执行函数”来实现块作用域。

((function() {
    const foo = function() {
        return 1;
    }
    console.log("foo()===1", foo() === 1);
    ((function() {
        const foo = function() {
            return 2;
        }
        console.log("foo()===2", foo() === 2);
    })())
})())

结果:

ES6入门篇 - 由浅入深对比ES5

这样即实现了块作用域,互不干扰。而在ES6中的块作用域呢?很简单,接着看。

ES6中块作用域

在ES6中指定一个块作用域,直接使用“花括号”即可。

{
    function foo() {
        return 1;
    }
    console.log("foo()===1", foo() === 1);

    {
        function foo() {
            return 2;
        }
        console.log("foo()===2", foo() === 2);
    }

    console.log("foo()===1", foo() === 1);
}

结果:

ES6入门篇 - 由浅入深对比ES5

是不是就相当清晰和明了了,互不干扰。

箭头函数

箭头函数其实就是函数新的语法,在ES5及之前,声明函数都是使用function:

function name(){

}

但在ES6中,可以这样声明函数:

()=>{

}

那这样做有什么好处呢?一是代码减少了,不用再用function来声明,第二个就是this的指向有了新的意义,下面用遍历操作来举例说明。

ES5声明函数

var evens = [1, 2, 3, 4, 5];
var odds = evens.map(function(v) {
  return v + 1;
});
console.log(evens, odds);

结果:

ES6入门篇 - 由浅入深对比ES5

ES6箭头函数

{
  let evens = [1, 2, 3, 4, 5];
  let odds = evens.map(v => v + 1);
  console.log(evens, odds);
}

结果:

ES6入门篇 - 由浅入深对比ES5

这里的v => v + 1箭头函数和上面的箭头函数有点不一样啊?其实这样的写法是很正确的。

箭头函数是在小括号()中声明参数,如果参数只有一个,小括号()是可以省略的;花括号{}中是写表达式的,如果花括号{}中的表达式直接作为返回值的话,是可以省略花括号{}的。

ES5 this指向

var factory = function() {
  this.a = 'a';
  this.b = 'b';
  this.c = {
    a: 'a+',
    b: function() {
      return this.a;
    }
  }
}
console.log(new factory().c.b());

结果:

a+

this指向是该函数被调用的对象,函数执行时,this指向的是调用者的function,这里的b是c调用的,this.a就是c中的a,即为a+。

ES6 this指向

var factory = function() {
  this.a = 'a';
  this.b = 'b';
  this.c = {
    a: 'a+',
    b: () => {
      return this.a;
    }
  }
}
console.log(new factory().c.b());

结果为:

a

这里this指向结果就和上面的不一样了,箭头函数的this指向是定义时this指向,箭头函数中的thisthis.a指向的就是

var factory = function() {

}

函数体中的this,函数体中的this指向的就是构造函数中的实例factory

所以

b: () => {
  return this.a;
}

中的this.a指向的是就是new factory()的a了。

总而言之就是: obj.f()通常函数f里面的this指向的是调用者obj,但是在箭头函数里面this指向的是定义的对象。

默认参数

ES5中默认参数

需要判断是否传值,未传值则使用默认值,有点麻烦。

function f(x, y, z) {
  if (y === undefined) {
    y = 7;
  }
  z = z || 42;
  return x + y + z;
}
console.log(f(1));

结果:

46

ES6中默认参数

function f(x, y = 7, z = 42) {
  return x + y + z;
}
console.log(f(1, 3));

结果:

46

必选参数校验

function checkParameter() {
  throw new Error('can\'t be empty');
}
function f(x = checkParameter(), y = 7, z = 42) {
  return x + y + z;
}
console.log(f(1));
try {
  f();
} catch (e) {
  console.log(e);
}

结果:

ES6入门篇 - 由浅入深对比ES5

ES5可变参数的处理

不确定参数的个数,就需要可变参数,下例为求和方法。

function f() {
  var a = Array.prototype.slice.call(arguments);
  var sum = 0;
  a.forEach(function(item) {
    sum += item * 1;
  })
  return sum;
}
console.log(f(1, 2, 3, 6));

结果:

12

ES6可变参数的处理

function f(...a) {
  var sum = 0;
  a.forEach(item => {
    sum += item * 1;
  });
  return sum;
}
console.log(f(1, 2, 3, 6));

结果:

12

传入参数中的...是扩展运算符 可变参数,即为一个数组。

ES5合并数组

合并两个数组内容

var params = ['hello', true, 7];
var other = [1, 2].concat(params);
console.log(other);

结果:

ES6入门篇 - 由浅入深对比ES5

ES6合并数组

var params = ['hello', true, 7];
var other = [
  1, 2, ...params
];
console.log(other);

结果:

ES6入门篇 - 由浅入深对比ES5

对象代理

ES3/5数据保护

通过set方法来限制修改。

var Person = function() {
  var data = {
    name: 'es3',
    sex: 'male',
    age: 15
  }
  this.get = function(key) {
    return data[key]
  }
  this.set = function(key, value) {
    if (key !== 'sex') {
      data[key] = value
    }
  }
}

// 声明一个实例
var person = new Person();
// 读取
console.table({name: person.get('name'), sex: person.get('sex'), age: person.get('age')});
// 修改
person.set('name', 'es3-cname');
person.set('sex', 'female');
console.table({
  name: person.get('name'), 
  sex: person.get('sex'),
  age: person.get('age')
});

结果:

setSex并没有起作用。

ES6入门篇 - 由浅入深对比ES5

ES5数据保护

利用Object.defineProperty API来操作只读,就不能写了。

var Person = {
  name: 'es5',
  age: 15
};
Object.defineProperty(Person, 'sex', {
  writable: false,
  value: 'male'
});
console.table({name: Person.name, age: Person.age, sex: Person.sex});
try {
  Person.name = 'es5-cname';
  Person.sex = 'female';
  console.table({name: Person.name, age: Person.age, sex: Person.sex});
} catch (e) {
  console.log(e);
}Z

结果:

ES6入门篇 - 由浅入深对比ES5

ES6数据保护

使用Proxy代理对象来限制读写。

let Person = {
  name: 'es6',
  sex: 'male',
  age: 15
};

let person = new Proxy(Person, {
  get(target, key) {
    return target[key];
  },
  set(target,key,value){
    if(key!=='sex'){
      target[key]=value;
    }
  }
});

console.table({
  name:person.name,
  sex:person.sex,
  age:person.age
});

try {
  person.sex='female';
} catch (e) {
  console.log(e);
}

结果:

ES6入门篇 - 由浅入深对比ES5