ES6函数扩展

ES6函数扩展

函数默认值

ES6可以直接在把默认值写在参数后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

参数是默认声明,不能再使用let或者const

默认允许使用解构赋值

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined

格式不正确会报错。

对象解构默认值

// 写法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

写法一的意思是函数参数的默认值是空对象,但设置了对象解构赋值的默认值。

写法二的函数参数默认值是一个具体属性的对象但没有设置对象解构赋值的默认值。

通常情况下,定义了默认值的参数,应该是函数的尾参数。 只有参数严格等于undefined,默认值才会生效。

函数的length属性

函数的length属性,不会计算有默认值以及其后面的参数个数。

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

作用域

如果一个函数的默认值是一个变量,该变量的作用域是先函数作用域,再全局作用域。

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代码中,函数调用时,y的默认值变量x尚未在函数内部生成,所以x指向全局变量。 如果全局全局作用域不存在,就会报错。

参数默认值设为undefined,表明这个参数是可以省略的。

rest参数

rest参数

rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 函数的length属性,不包括rest参数。

扩展运算符

它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

扩展运算符

主要用于函数的调用。

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

只要含有Iterator接口的对象,都可以用扩展运算符转为真正的数组。

var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

箭头函数

ES6允许使用“箭头”(=>)定义函数。

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

rest参数与箭头函数结合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

注意:

  1. 没有 thissuperarguments ,也没有 new.target 绑定: this 、 super 、 arguments 、以及函数内部的 new.target 的值由外层最近的非箭头函数来决定

  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数。

实际上箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
//等同于
// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

绑定this

ES7提案中有,函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

foo::bar;
// 等同于
bar.bind(foo);

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

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

尾调用

指某个函数的最后一步是调用另一个函数。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f(x) 的调用帧,只保留 g(3) 的调用帧。

这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

参考

原型对象创建与修改

Object.create()

Object.create()可创建一个具有指定原型且可选择性地包含指定属性的对象。

Object.create(proto, [ propertiesObject ])
  • proto

    • 一个对象,新创建的对象的原型。

  • propertiesObject

    • 可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符

o = {};
// 以字面量方式创建的空对象就相当于:
o = Object.create(Object.prototype);

=================================

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
o = Object.create({}, { p: { value: 42 } })

// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
o.p = 24
o.p
//42

Object.setPrototypeOf()

let person = {
    getGreeting() {
        return "Hello";
    }
};

let dog = {
    getGreeting() {
        return "Woof";
    }
};

// 原型为 person
let friend = Object.create(person);
console.log(friend.getGreeting());                      // "Hello"

// 将原型设置为 dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());                      // "Woof"

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

Last updated