ES6 函数与对象(四)

ES6在函数,对象,类上的新增特性

1. 函数扩展

之前已经说过,函数参数支持设置默认值,而且也支持解构赋值,所以这里我们就不讲了

  • 函数的length

函数有length属性表示函数的参数个数,但是如果参数有默认值,length属性表示函数没有默认值之前的参数个数

function a(x,y,z){}
a.length
// 3

function b(x=1, y, z) {}
b.length
// 0

function b(x, y=1, z=1) {}
b.length
// 1 
  • 设置默认值后的作用域

在设置默认值后,函数的参数会形成单独的作用域

var x = 1;
function foo(x, y = function() { x = 2; }) {
 var x = 3;
 y();
 console.log(x);
}

foo() // 3
x // 1 
  • rest参数

ES6用以替代arguments的方案,arguments是类数组对象,而rest参数是数组

之前我们这样写

function sum() {
   var s = 0;
   for(var i = 0; i < arguments.length; i++) {
       s+=arguments[i];
   }
   return s;
} 

现在我们可以这样写

function sum(...args) {
   var s = 0;
   for(var i = 0; i < args.length; i++) {
       s += args[i]; 
   }
   return s;
} 

当然其实rest的参数还可以用于剩余参数,例如

function push(array, ...items) {
 items.forEach(function(item) {
   array.push(item);
   console.log(item);
 });
}

var a = [];
push(a, 1, 2, 3); 

注意,这样不行

function a(...x, y) {} // 报错 

而且函数的length会忽略rest参数

  • 扩展运算符

扩展运算符好比rest参数的逆运算,rest参数将参数合并为一个数组,而运算符则是将数组分解成一项项

它的一个十分作用就是减少我们丑陋的apply方法调用,例如,以前我们这样写

var a = [1,2,3];
Math.max.apply(Math, a); 

现在我们这样写

var a = [1,2,3];
Math.max(...a); 

我们还可以用它合并数组

var a = [1, 2, 3];
var b = [4, 5, 6];
a = [...a,...b]
// [1, 2, 3, 4, 5, 6] 

也可以和解构赋值结合

var [a, ...b] = [1, 2, 3, 4];
b
// [2, 3, 4] 

而且能够将字符串转化为真正的数组

[...'hello']
// [ "h", "e", "l", "l", "o" ] 

而且能够正确识别32位Unicode字符

'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3 

任何有Iterator接口的对象都可以使用扩展运算符转化为真正的数组,比如Map,Set

  • 严格模式

ES5可以在函数内部定义严格模式,但是在ES6中有几个例外

  1. 函数参数中默认值
  2. 函数参数有解构赋值
  3. 函数参数中有rest参数

因为这些情况会形成一个作用域,并会先于函数体中的内容执行,所以这些情况不能定义严格模式

  • 函数的name属性

ES6的匿名函数也可以获得其name属性

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f" 
  • 箭头函数

基本用法如下:

// 1. 简单的例子
var a = v=>v;
// 等同于
var a = function(v) {return v};

// 2. 无参数
var b = ()=>5;
// 等同于
var b = function(){return 5;};

// 3. 多参数
var c = (x,y,z)=>x+y+z;
// 等同于
var c = function(x,y,z) {return x+y+z;};

// 4. 函数体内多语句
var d = (x,y,z)=>{
   var i = 2;
   return i*x*y*z;
}
// 等同于
var d = function(x, y ,z) {
   var i = 2;
   return i*x*y*z;
}

// 5. 和解构赋值结合

var f = ({a, b})=>a+b;
// 等同于
var f = function({a,b}){return a+b;} 

箭头函数还有一个超级无敌功能:this绑定

箭头函数本身是没有this,它的this指向定义时所在的作用域

function a(){
   this.x = 1;
   setTimeout(()=>{
   	console.log(this.x);
   }, 1000)
}
new a();
// 1 

如果是普通函数

function a(){
   this.x = 1;
   setTimeout(function(){
   	console.log(this.x);
   }, 1000)
}
new a();
// undefined 

还有一个管道部署的例子,前一个函数的输出是后一个函数的输入

const pipeline = (...funcs) =>
 val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);

addThenMult(5)
// 12 

还有几个注意点

  1. 不能将其作为构造函数
  2. 不能使用arguments对象,函数体内不存在这个对象
  3. 不能使用yield
  • 函数绑定运算符ES7 babel支持转码

箭头函数能够解决部分的this绑定问题,但不能解决所有,所以就有了这个函数绑定运算符

var Obj = {
 name : "jeff",
 do: function(){
   return this.name;
 }
};

var {name} = Obj; // 这里的name已经失去了对象上下文
Obj::name; // 使用函数绑定运算符进行绑定 

babel转码器转码如下:

var Obj = {
 name: "jeff",
 "do": function _do() {
   return this.name;
 }
};

var name = Obj.name;

name.bind(Obj); 

2. 对象扩展

  • 对象简洁表示
// 1. 属性简写
var a = "b";

var b = {a};
// 等同于
var b = {a:a};



// 2. 方法简写

var a = {
 get (){
    return 1;
 }  
};
// 等同于
var a = {
   "get": function(){
       return 1;
   }
} 

这种简洁的写法可以避免很多多余的写法,

例如:

var ms = {};

function getItem (key) {
 return key in ms ? ms[key] : null;
}

function setItem (key, value) {
 ms[key] = value;
}

function clear () {
 ms = {};
}

module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
 getItem: getItem,
 setItem: setItem,
 clear: clear
}; 
  • 属性名表达式

ES5中我们不可以在属性名中执行表达式,但是在ES6中我们可以

var a = "p";
var b = {
   [a]: 1
};
b[a]
// 1 

属性名甚至可以是对象,很是奇怪

var p = {
   p:1
};
var o = {
 [p]:"x"  
};
o[p]
// x
o
// Object {[object Object]: "x"} 

可能是将对象的引用值用以作为属性名吧

  • Object.is()
    因为NaN!==NaN,而==会自动转换数据类型,所以为了解决比较不同意的情况,添加了这个方法
Object.is(NaN, NaN); // true 

不过要注意

Object.is(+0, -0); 
// false 
  • Object.assign()

这个方法可以进行对象浅克隆,第一个参数是目标对象,之后的参数是源对象

注意,如果目标对象和源对象存在同名属性,源对象的属性会覆盖目标对象的属性

至于这个方法的用途,相信使用过jQuery框架中的$.extend()的人应该知道

  • Object.getPrototypeOf()Object.setPrototypeOf()

这两个方法相当于__proto__参数的get方法和set方法

var x = function(){};
var o = {};
x.prototype = o;
var i = new x();
Object.getPrototypeOf(i) === o;
// true 
var o = {};
var p = {x:1};
Object.setPrototypeOf(o, p);
o.x
// 1 
  • Object.getOwnPropertyDescriptors()

获取对象所有属性的所有属性特性

var o = {a:1, b:2};
Object.getOwnPropertyDescriptors(o);
//////////////////////////////////
{
   a:{
       value: 1,
       writable: true,
       enumerable: true,
       configurable: true
   },
   b:{
       value: 2,
       writable: true,
       enumerable: true,
       configurable: true
   }
} 

3. class

class是一个语法糖,它是的js中OOP编程更加规范,更加容易(哎,可能是要迎合大量OOP编程人员的需要吧)

  • 基本用法
class Parent {
   constructor (x, y) { // 构造器
       this.x = x;
       this.y = y;
   }
   
   // ES6中没有实例属性和静态属性,但是Babel支持
   // name = "x";实例属性
   // static name = "x";静态属性
   
   work (){  // Parent类的实例方法
       console.log("work");
   }
   static getName(){ // 静态方法
       return "jeff";
   }
   toString() { // 不同于ES5, toString这种方法不可枚举
       return "Parent";
   }
}

// Child继承于Parent
class Child extends Parent {
   constructor(x, y) {
       super(x, y); // 必须第一句写super
   }
   work() {
       super.work();
       console.log("for Parent);
   }
} 
  • super的用法

super只能在class中使用,super有两种完全不同的用法,一个当做函数使用,一个当做对象使用

子类的构造器中必须第一时间执行super(),这个时候super是函数,只能在构造器中使用

而在其他方法中,super指向父类的原型对象,而且会绑定子类的this对象

  • 继承内置对象

class可以继承内置对象

class MyArray extends Array {
 constructor(...args) {
   super(...args);
 }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined 
  • 使用存取器属性
class MyClass {
 constructor() {
   // ...
 }
 get prop() {
   return 'getter';
 }
 set prop(value) {
   console.log('setter: '+value);
 }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter' 
  • 模拟class
// 参考babel
// 模拟super
function get(obj, prop) {
   var flag = true;
   while(flag) {
       var a = obj,
           b = prop
       flag = false;
       if(a==null) a = Function.prototype;
       var desc = Object.getOwnPropertyDescriptor(a, b);
       // 属性特性不存在,沿着原型链继续找
       if(!desc) {
           var parent = Object.getPrototypeOf(a);
           if(parent===null) {
               return undefined;
           } else {
               obj = parent;
               desc = parent = undefined;
               flag = true;
               continue;
           }
       // 如果有值
       } else if("value" in desc) {
           return desc.value;
       // 属性有存取器属性
       } else {
           var getter = desc.get;
           if(!getter) return undefined;
           return desc.get;
       }
   }
   
}



// 模拟extends

function inherit(superClass, subClass){
   if(typeof superClass !== "function" 
   || superClass !== null)
   throw new TypeError("super class must be function or null");
   subClass.prototype = Object.create(superClass.prototype);
   // 如果有ES6的setPrototype
   if(Object.setPrototypeOf) {
       Object.setPrototypeOf(subClass, superCLass);
   } else {
       subClass.__proto__ = superClass;
   }
} 
  • new.target

用以判断类是否使用new实例化

function Person(name) {
 if (new.target !== undefined) {
   this.name = name;
 } else {
   throw new Error('必须使用new生成实例');
 }
}

// 另一种写法
function Person(name) {
 if (new.target === Person) {
   this.name = name;
 } else {
   throw new Error('必须使用new生成实例');
 }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错 

参考文献


write by jeffwang 2017/03/20