文章目录
一、Javascript函数
1.1 带参数默认值的函数
JS函数的独特之处是可以接受任意数量的参数,而无视函数声明处的参数数量。这让你定义的函数可以命名用不同的参数数量来调用,调用时未提供的参数经常会使用默认值来代替。
// 在ES5中模拟参数默认值
function func01(url, timeout,callback){
timeout = timeout || 2000
callback = callback || function(){}
// 函数的余下部分
}
// timeout与callback实际上都是可选参数,因为他们都会在参数未被提供的情况下使用
// 默认值。逻辑或运算符(||)在左侧的值为假的情况下总会返回右侧的操作数。由于函数
// 的具名参数在未被明确提供时会是undefined,逻辑或运算符就经常被用来给缺失的参数
// 提供默认值。不过此方法有个瑕疵,在此处的timeout的有效值实际上有可能是0,但是
// 因为0是假值,就会导致timeout的值在这种情况下会被替换为2000.
// 可使用更安全的替代方法实现
function func02(url, timeout, callback){
timeout = (typeof timeout !== 'undefined') ? timeout : 2000
callback = (typeof callback !== 'undefined') ? callback : function(){}
// ......
}
// ES6中的参数默认值
// ES6能更容易地为参数提供默认值,它使用了初始化形式,以便在参数未被正确传递进来时使用
function func03(url, timeout=2000, callback=function(){}){
//......
}
let value = 5
function getValue(){
return value++
}
function add(first, second=getValue()){
return first + second
}
console.log(add(1, 1)) //2
console.log(add(1)) //6
console.log(add(1)) //7
代码讲解
将函数调用作为参数的默认值时需要小心,如果你遗漏了括号,例如在上面盒子中使用
second=getValue,你就传递了对于该函数的一个引用,而没有传递调用函数的结果。
1.2 剩余参数
剩余参数( rest parameter )由三个点( … )与一个紧跟着的具名参数指定,它会是包含传递给函数的其余参数的一个数组,名称中的“剩余”也由此而来。
剩余参数的限制条件
剩余参数受到两点限制。一是函数只能有一个剩余参数,并且它必须被放在最后。
function func01(name, ...args){
let result = ''
for(let i=0; i<args.length; i++){
result += ' '+args[i]
}
return name + ',' + result
}
console.log(func01('张三', '李四', '王五'));
1.3 函数构造器
Function 构造器允许你动态创建一个新函数,但在 JS 中并不常用。传给该构造器的参数都是字符串,它们就是目标函数的参数与函数体。
let add = new Function('first', 'second', 'return first+second')
console.log(add(1, 2))
ES6 增强了 Function 构造器的能力,允许使用默认参数以及剩余参数。对于默认参数来说,你只需为参数名称添加等于符号以及默认值
let add = new Function('first', 'second=first', 'return first+second')
console.log(add(1, 1)) //2
console.log(add(1)) //2
1.4 扩展运算符
与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。
取出数组中的最大值
// ES5或更早版本JS
let values = [25, 50, 75, 100]
console.log(Math.max.apply(Math, values))
代码讲解:该解决方案是可行的,但如此使用 apply() 会让人有一点疑惑,它实际上使用了额外的语法混淆了代码的真实意图。
ES6 的扩展运算符令这种情况变得简单。无须调用 apply() ,你可以像使用剩余参数那样在该数组前添加 … ,并直接将其传递给 Math.max() 。 JS 引擎将会将该数组分割为独立参数并把它们传递进去,就像这样:
// 等价于console.log(Math.max(25, 50, 75, 100))
console.log(Math.max(...values)) //扩展运算符
1.5 ES6 的名称属性
定义函数有各种各样的方式,在 JS 中识别函数就变得很有挑战性。此外,匿名函数表达式的流行使得调试有点困难,经常导致堆栈跟踪难以被阅读与解释。正因为此, ES6 给所有函数添加了 name 属性。
1.6 明确函数的双重用途
在 ES5 以及更早版本中,函数根据是否使用 new 来调用而有双重用途。当使用 new 时,函数内部的 this 是一个新对象,并作为函数的返回值,如下例所示:
function Person(name){
this.name = name
}
const person = new Person('kangyun')
const notAPerson = Person('kangyun')
console.log(person) // Object
console.log(notAPerson) // undefined
代码讲解:
当创建 notAPerson 时,未使用 new 来调用 Person() ,输出了 undefined (并且在非严格模式下给全局对象添加了 name 属性)。 Person 首字母大写是指示其应当使用 new 来调用的唯一标识,这在 JS 编程中十分普遍。函数双重角色的混乱情况在 ES6 中发生了一些改变。
JS 为函数提供了两个不同的内部方法: [[Call]] 与 [[Construct]] 。当函数未使用 new进行调用时, [[call]] 方法会被执行,运行的是代码中显示的函数体。而当函数使用 new 进行调用时, [[Construct]] 方法则会被执行,负责创建一个被称为新目标的新的对象,并且使用该新目标作为 this 去执行函数体。拥有 [[Construct]] 方法的函数被称为构造器。
记住并不是所有函数都拥有 [[Construct]] 方法,因此不是所有函数都可以用 new 来调用。在“箭头函数”小节中介绍的箭头函数就未拥有该方法。
- 在 ES5 中判断函数如何被调用
在 ES5 中判断函数是不是使用了 new 来调用(即作为构造器),最流行的方式是使用instanceof
function Person(name){
if(this instanceof Person){
this.name = name
}else{
throw new Error("You must use new with Person")
}
}
const person = new Person('kangyun')
const notAPerson = Person('kangyun') //抛出错误
此处对 this 值进行了检查,来判断其是否为构造器的一个实例:若是,正常继续执行;否则抛出错误。这能奏效是因为 [[Construct]] 方法创建了 Person 的一个新实例并将其赋值给 this 。可惜的是,该方法并不绝对可靠,因为在不使用 new 的情况下 this 仍然可能是 Person 的实例,正如下例:
function Person(name){
if(this instanceof Person){
this.name = name
}else{
throw new Error("You must use new with Person")
}
}
const person = new Person('kangyun')
const notAPerson = Person.call(person, 'kangyun') // ok
调用 Person.call() 并将 person 变量作为第一个参数传入,这意味着将 Person 内部的
this 设置为了 person 。对于该函数来说,没有任何方法能将这种方式与使用 new 调用区分开来。
1.7 new.target 元属性
为了解决这个问题, ES6 引入了 new.target 元属性。元属性指的是“非对象”(例如 new )上的一个属性,并提供关联到它的目标的附加信息。当函数的 [[Construct]] 方法被调用时, new.target 会被填入 new 运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的 this 值。而若 [[Call]] 被执行, new.target 的值则会是 undefined 。
通过检查 new.target 是否被定义,这个新的元属性就让你能安全地判断函数是否被使用new 进行了调用
function Person(name){
if(typeof new.target!=='undefined'){
this.name = name
}else{
throw new Error("You must use new with Person")
}
}
const person = new Person('kangyun')
const notAPerson = Person.call(person, 'kangyun') // 出错了
ES6 通过新增 new.target 而消除了函数调用方面的不确定性。
1.8 块级函数
块级函数与 let 函数表达式相似,在执行流跳出定义所在的代码块之后,函数定义就会被移除。关键区别在于:块级函数会被提升到所在代码块的顶部;而使用 let 的函数表达式则不会,正如以下范例所示:
if(true){
console.log(typeof doSomething) // 抛出错误
let doSomething = function(){
//......
}
doSomething()
}
console.log(typeof doSomething)
此处代码在 typeof doSomething 被执行时中断了,因为 let 声明尚未被执行,将
doSomething() 放入了暂时性死区。知道这个区别之后,你就可以根据是否想要提升来选择应当使用块级函数还是 let 表达式。
ES6 在非严格模式下同样允许使用块级函数,但行为有细微不同。块级函数的作用域会被提升到所在函数或全局环境的顶部,而不是代码块的顶部。
if(true){
console.log(typeof doSomething) // function
function doSomething(){
//......
}
doSomething()
}
console.log(typeof doSomething) // function
1.9 箭头函数
ES6 最有意思的一个新部分就是箭头函数( arrow function )。箭头函数正如名称所示那样使用一个“箭头”( => )来定义,但它的行为在很多重要方面与传统的 JS 函数不同
- 没有 this 、 super 、 arguments ,也没有 new.target 绑定: this 、 super 、arguments 、以及函数内部的 new.target 的值由所在的、最靠近的非箭头函数来决定 。
- 不能被使用 new 调用: 箭头函数没有 [[Construct]] 方法,因此不能被用为构造函数,使用 new 调用箭头函数会抛出错误。
- 没有原型: 既然不能对箭头函数使用 new ,那么它也不需要原型,也就是没有 prototype 属性。
- 不能更改 this : this 的值在函数内部不能被修改,在函数的整个生命周期内其值会保持不变。
- 没有 arguments 对象: 既然箭头函数没有 arguments 绑定,你必须依赖于具名参数或剩余参数来访问函数的参数。
- 不允许重复的具名参数: 箭头函数不允许拥有重复的具名参数,无论是否在严格模式下;而相对来说,传统函数只有在严格模式下才禁止这种重复。
花括号被用于表示函数的主体,它在你至今看到的例子中都工作正常。但若箭头函数想要从函数体内向外返回一个对象字面量,就必须将该字面量包裹在圆括号内,例如:
const getObject01 = id => ({id: 10, name: 'kangyun'})
//等价于
const getObject02 = function(id){
return {
id: 10,
name: 'kangyun'
}
}
将对象字面量包裹在括号内,标示了括号内是一个字面量而不是函数体。
箭头函数没有 this 绑定,意味着箭头函数内部的 this 值只能通过查找作用域链来确定。如果箭头函数被包含在一个非箭头函数内,那么 this 值就会与该函数的相等;否则,this 值就会是全局对象(在浏览器中是 window ,在 nodejs 中是 global )
const PageHandler = {
id: '123456',
init: function(){
//箭头函数在一个普通函数中,它的this和普通函数的this相同
document.addEventListener("click", event=>this.doSomething(event.type), false)
},
doSomething: function(type){
console.log('Handling' + type + ' for ' + this.id)
}
}
1.10 箭头函数与数组
箭头函数的简洁语法也让它成为进行数组操作的理想选择。
const values = [2, 3, 1, 90, 70, 100]
let result = values.sort((a, b)=>a-b);
console.log(result)
1.11 没有 arguments 绑定
尽管箭头函数没有自己的 arguments 对象,但仍然能访问包含它的函数的 arguments 对象。无论此后箭头函数在何处执行,该对象都是可用的
function func(){
return () => arguments[0]
}
let testFunc = func(5)
console.log(testFunc())
尽管箭头函数 testFunc 已不在创建它的函数的作用域内,但由于 arguments 标识符的作用域链解析, arguments 对象依然可被访问。
箭头函数能在任意位置替代你当前使用的匿名函数,例如回调函数。