接上一章函数上
内容。
- 处理无命名参数
- 增强的Function构造函数
- 展开运算符
- name属性
- 明确函数的多重用途
- 块级函数
处理无命名参数
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。
不定参数的使用限制
不定参数有两条使用限制。
- 每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。
- 不定参数不能用于对象字面量setter之中。(因为对象字面量setter的参数有且只能有一个,但是不定参数中参数数量可以无限多,所以在当前上下文中不允许使用不定参数)
针对这两点,看以下实例:
// 每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。
// 语法错误
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关键字被调用。
- ES5中判断函数被调用的方法,最流行的是使用instanceof。
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实例。
- 元属性(Metaproperty)new.target:ES6通过new.target这个元属性(元属性是指非对象的属性,其可以提供非对象目标的补充信息(比如new))函数外使用new.target是一个语法错误
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()方法反反复复看了很多遍。
网友评论