函数(中)

2019-10-17  本文已影响0人  NnnLillian

接上一章函数上
内容。


处理无命名参数

JS的函数语法规定,无论函数已定义的命名参数有多少,都不限制调用时传入的实际参数数量,调用时总是可以传入任意数量的参数。

ES5中的无命名参数

早先,JavaScripe提供arguments对象来检查函数的所有参数,从而不必定义每一个要用的参数。

// 模仿Underscore.js库中的pick()方法,返回一个给定对象的副本,包含原始对象属性的特定子集。
function pick(object) {
    var result = Object.create(null); // 创建一个对象
    // 从第二个参数开始
    for (var i = 1, len = arguments.length; i < len; i++) {
        result[arguments[i]] = object[arguments[i]];
    }
    return result;
}
// arguments将object也计入,所以除开第一个参数,要从索引1而不是索引0开始遍历arguments对象。
var book = {
    title: "ES5",
    author: "Nicholas C.Zakes",
    year: 2016
};
var o = pick(book, "author", "year");
o.author; // "Nicholas C.Zakes"
o.year; // 2016

其中Object.create(null)来初始化一个新对象。为什么不用更简洁的{}。详解可以看详解Object.create(null)

不定参数

在ES6中,引入不定参数(rest parameters)的特性。在函数的命名参数前添加三个点(...)就表明这是一个不定参数,该参数为一个数组,包含着自他之后传入的所有参数,通过这个数组名即可逐一访问里面的参数。

function pick(object, ...keys) {
    var result = Object.create(null); // 创建一个对象
    for (var i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }
    return result;
}
// keys将object不计入在其内
var book = {
    title: "understanding ES6",
    author: "Nicholas C.Zakes",
    year: 2016
};
var o = pick(book, "author", "year");

console.log(pick.length); // ->1
o.author; // "Nicholas C.Zakes"
o.year; // 2016

与上一个函数不同,不定参数keys包含的是object之后传入的所有参数,而arguments对象包含的则是所有传入的参数,包括object。

注意:函数的length属性统计的是函数命名参数的数量,不定参数的加入不会影响length属性的值,本例中,pick.length的值为1,因为只会计算object。

不定参数的使用限制

不定参数有两条使用限制。

针对这两点,看以下实例:

// 每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。
// 语法错误
function pick(object, ...keys, last) { 
    //...
}

// 不定参数不能用于对象字面量setter之中。
let object = {
     // 语法错误:不可以在setter中使用不定参数
    set name(...value) {
        // do something
    }
};
不定参数对arguments对象的影响

如果声明函数时定义了不定函数,则在函数被调用时,arguments对象包含了所有传入函数的参数。

function checkArgs(...args){
   console.log(args.length); // 2
   console.log(arguments.length); // 2
   console.log(args[0], arguments[0]); // "a" "a"
       console.log(args[0], arguments[0]); // "b" "b"
}

checkArgs("a", "b");

无论是否使用不定参数,arguments对象总是包含所有传入函数的参数。

增强的Function构造函数

Function构造函数是JavaScript语法中很少被用到的部分,通常用来动态创建新的函数。
构造函数接受字符串形式的参数,分别为函数的参数以及函数体。

var add = new Function("a", "b", "return a+b");
console.log(add(1, 1)); // 2

ES6增强了Function构造函数的功能,支持在创建函数时定义默认参数不定参数。这两个特性使得Function构造函数具备了与声明式创建函数相同的能力。

var add = new Function("a", "b = a", "return a+b");
console.log(add(2));// 4

// 定义不定参数
var pick = new Function("...a","return a[0]");
console.log(pick(1, 2));// 1

展开运算符

不定参数可以指定多个各自独立的参数,并通过整合后的数组来访问;展开运算符可以指定一个数据,将它们打算后作为各自独立的参数传入函数。

假设现在需要从一个数组中选出最大的数字,在ES5中,要么手动实现从数组中遍历取值,要么这样引用apply()方法。apply()方法没有很容易去理解,可以参考文章:【优雅代码】深入浅出 妙用Javascript中apply、call、bind

var numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers);  //458

ES6中的展开运算符就可以简化上述示例。

var  numbers = [5, 458 , 120 , -215 ]; 
console.log(Math.max(...numbers)); //458

//可以将展开运算符与其他正常传入的参数混合使用
console.log(Math.max(...numbers, 500)); // 500

name属性

由于JS中有多种定义函数的方式,所以辨别函数是一项具有挑战性的任务,为解决该问题,ES6为所有函数新增了name属性。

name属性名称以及特殊情况

// 函数
function doSomething(){}
console.log(doSomething.name); // "doSomething"

// 函数表达式
var doAnotherThing = function() {};
console.log(doAnotherThing.name);// "doAnotherThing"

// 函数表达式有一个name,这个name比函数本身被赋值的变量的权重高,所以doOnething.name的值为"doOnethingElse"
var doOnething = function doOnethingElse(){};
console.log(doOnething.name);// "doOnethingElse"

// person.sayName()的name属性的值取自对象字面量
// person.sayName()实际上是一个getter函数,
var person = {
  sayName: function() {
    console.log(this.name);
  },
  get firstName() {
    return "baby"
  }
}
console.log(person.firstName.name); // "get firstName"
console.log(person.sayName.name); // "sayName"

// 通过bind()函数创建的函数,其名称将带有"bound"前缀;
// 通过Function构造函数创建的函数,其名称将是"anonymous"
var doJustThing = function(){};
// 绑定函数的name属性总是由被绑定函数的name属性及字符串前缀"bound"组成
console.log(doJustThing.bind().name);// "bound doJustThing"
console.log((new Function()).name);// "anonymous"

明确函数的多重运用

ES5中的函数具有多重功能,可以结合new使用,函数内的this值将会指向一个新对象,函数最终会返回这个新对象。

function Person(name){
    this.name = name;
}

var person = new Person("baby");
var notNewPerson = Person("baby");

console.log(person); // [object Object]
console.log(notNewPerson);// undefined

notNewPerson没有通过new关键字调用Person(),返回undefined,只有通过new关键字调用Person()时才能体现其能力。
函数内部有两个方法 [[call]] 和 [[construct]] (箭头函数没有这个方法),当使用new 操作符时, 函数内部调用 [[construct]], 创建一个新实例,this指向这个实例,再执行函数体; 不使用new 操作符时, 函数内部调用 [[call]],从而直接执行代码中的函数体。
具有 [[construct]]方法的函数被统称为构造函数。

如果想确定一个函数是否通过new关键字被调用。

function Person(name){
  // 检查this的值,是否为构造函数的实例,如果是,进入if
  if(this instanceof Person){
    this.name = name; // 如果通过new关键字调用
  } else {
    throw new Error("必需通过new关键字调用")
  }
}

var person = new Person("baby");  
var notNewPerson = Person("baby"); // Error: 必需通过new关键字调用

但是调用Person.call()或者apply()时,将变量person作为第一个参数传入,相当于再Person函数里将this指向了person实例,但是对于函数来说无法判断是通过call()/apply()还是关键字调用得到了Person实例。

function Person(name){
  // 检查new.target是否被定义来检测函数是否通过new关键字调用
  if(typeof new.target !== "undefined"){
    this.name = name; // 如果通过new关键字调用
  } else {
    throw new Error("必需通过new关键字调用")
  }
}

var person = new Person("baby");  
var notNewPerson = Person("baby"); // Error: 必需通过new关键字调用

检查new.target是否被某个特定的构造函数所调用

function Person(name){
  if(new.target === Person){
    this.name = name; // 如果通过new关键字调用
  } else {
    throw new Error("必需通过new关键字调用")
  }
}

var person = new Person("baby");
var notNewPerson = Person("baby");

块级函数

ES5严格模式下,在代码块内部声明函数时程序会抛出错误。
ES6中会将在代码块内部声明的函数视作一个块级证明,从而可以在定义该函数的代码块内访问和调用他。

// ES6中
"use strict"

if(true){
  console.log(typeof doSomething); // "function"
  function doSometing(){};
  doSomething();
}
console.log(typeof doSomething); // "undefined"

在定义函数的代码块内,块级函数会被提升至顶部,所以typeof doSomething 的值为 "function"。

块级函数的使用场景

"use strict"

if(true){
  console.log(typeof doSomething); // "Error"
  let doSometing = function(){};
  doSomething();
}

块级函数会被提升至块的顶部,而let定义的函数表达式不会被提升。所以当代码执行到typeof doSomething时,由于此时尚未执行let声明语句,doSomething()还在当前块作用域的临时死区中,因此程序被迫中断。

非严格模式下的块级函数

ES6非严格模式下也可以声明块级函数,但其行为与严格模式下不同:这些函数不再提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部。

// ES6中
if(true){
    console.log(typeof doSomething); // "function"
    function doSometing(){};
    doSomething();
}
console.log(typeof doSomething); // "function"

在这个例子中,doSomething()函数被提升至全局作用域。

今天做的这个位置,让我感受到--图书馆的下午好晒啊😂。不知道是因为我this确实理解的不好,还是因为太晒了,bind()方法反反复复看了很多遍。

上一篇 下一篇

猜你喜欢

热点阅读