定义函数的几种方式
// 函数声明
function sum(num1,num2){
return num1 + num2
}
// 变量初始化
let sum = function(num1,num2){
return num1 + num2
}
// 箭头函数
let sum = (num1,num2)=>{
return num1 + num2
}
10.1 箭头函数
1.只有一个参数可以省略小括号
2.如果函数体只有一行代码,省略大括号会隐式返回表达式
let sum = (a,b) => a+b// 省略大括号
let printX = x => x// 省略小括号
箭头函数没有arguments,super和new.target.不能用作构造函数,也没有prototype属性
10.2 函数名
函数名就是指向函数的指针,所以一个函数可以有多个函数名
ES6中所有函数对象都会暴露一个只读的name属性,其中包含关于函数的信息(一般是标识符)
没有名称的函数会显示成空字符串,用Function创建的会被表示成"anonymous"
10.3 参数
ECMAScript不关心参数的个数,类型等.
传入的参数在解释器中被表现成一个数组arguments,可以在函数内部访问arguments去取得每一个参数值(箭头函数除外)
arguments参数可以和命名参数对象一起使用
let sum = function(a,b,c){
for(const i of arguments){
console.log(i);
}
}
sum(1,2,3,4)
箭头函数中的参数
箭头函数中只能用定义的命名参数访问
10.4 没有重载
ECMAScript没有函数签名,所以没有重载
如果定义了两个相同函数名,则后面的会覆盖前面的
10.5 默认参数值
ES6支持显式定义默认参数
定义的默认参数值不影响arguments参数
let sayName = function(name = 'zs'){
console.log( `My Name is ${arguments[0]}, ${name}` )
}
sayName()
sayName('ls')
默认参数作用域与暂时性死区
默认参数按照顺序进行初始化,因此后面的参数可以调用前面的,但前面的参数不能使用后面的(称为暂时性死区)
function make(a = 10,b = a){
// 相当于
// let a = 10
// let b = a
}
10.6 拓展参数与收集
10.6.1 拓展参数
可以使用解构函数把数组拆分,当作传入的参数
let value = [1,2,3,4]
function sum(){
let s = 0
for(const i of arguments){
s+=i
}
return s
}
sum(-1,...value)
// 等价于
sum(-1,1,2,3,4)
10.6.2 收集参数
在函数定义时,可以使用拓展操作符接收不同长度的独立参数
收集参数前如果还有命名参数,则会接收剩余参数
收集参数只能单独出现在最后一个参数
let value = [1,2,3,4]
function sum(...values){
return values.reduce( (x,y)=>x+y,0 )
}
// 可以
function sum(firstValue,...values){
// 函数体
}
// 不可以
function sum(...values,lastValue){
// 函数体
}
箭头函数虽然不支持arguments,但是支持收集参数的定义方式
10.7 函数声明与函数表达式
使用function定义函数时,即使函数的定义出现在调用之后,也不会报错.因为函数声明提升,函数声明被提升到了代码顶部
let value = [1,2,3,4]
sum(balue)
function sum(...values){
return values.reduce( (x,y)=>x+y,0 )
}
但是如果使用let的声明方式,则不行
10.8 函数作为值
函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方
所以函数可以作为参数,也可以作为返回值
10.9 函数内部
函数内部有三个特殊对象
arguments
this
new.target
10.9.1 arguments
arguments是一个类似数组的对象,包含调用函数传入的所有参数
arguments还有callee属性,指向arguments所在函数
function factorial(num){
if(num<=1){
return 1
} else {
return num * factorial(n-1)
// 使用 arguments.callee 可以使函数逻辑与函数名解耦
return num * arguments.callee(n-1)
}
}
10.9.2 this
this在标准函数和箭头函数中的行为不同
在标准函数中,this.引用的是把函数当成方法调用的上下文对象(在网页全局的函数,this指向window)
window.color = 'red'
let o = {
color: 'blue'
}
function sayColor(){
console.log(this.color);
}
sayColor()// red
o.sayColor = sayColor
o.sayColor()// blue
在箭头函数中,this保留定义函数时的上下文
function sayColor(){
this.color = 'red'
// 这里的this指向sayColor
setTimeout(()=>console.log(this.color),1000)
}
10.9.3 caller
caller的引用是调用当前函数的函数(在全局调用的则为null)
可以使用arguments.callee.caller来降低耦合度
function outer(){
inner()
}
function inner(){
console.log(inner.caller);
// 可以使用arguments.callee.caller来降低耦合度
console.log(arguments.callee.caller);
}
outer()
在严格模式下,不能给caller赋值
10.9.4 new.target
new.target 检测函数是否是用new调用的,如果是普通调用,显示undefined;如果使用了new,则音乐被调用的构造函数
10.10 函数属性与方法
每个函数都有两个属性:
length:保存函数定义时命名参数的个数
prototype:保存引用类型所有实例方法的地方.不可枚举
两个方法:
apply() 第一个参数接受this,第二个参数接受数组,也可以是arguments对象
会返回调用函数的值
function sum(a,b){
return a+b
}
console.log(sum.apply(this,[10,10]));// 20
call()和apply()一样,但是第二个参数必须一个一个列出来
function sum(a,b){
return a+b
}
console.log(sum.call(this,10,10));// 20
call()和apply()可以指定this值,可以把任何对象设置成函数作用域
window.color = 'red'
let o = {
color: 'blue'
}
function sayColor(){
console.log(this.color);
}
sayColor.call(window)
sayColor.call(this)
sayColor.call(o)
bind()函数会创建一个新的函数实例,并把其this值绑定到bind()对象
window.color = 'red'
let o = {
color: 'blue'
}
function sayColor(){
console.log(this.color);
}
sayColor.call(o)// blue
let objectSayColor = sayColor.bind(o)
objectSayColor()// blue
10.11 函数表达式
函数声明:函数声明提升
会在代码执行之前获得定义
函数表达式就像字面量赋值,又叫匿名函数(lambda函数)
匿名函数没有函数声明提升
// 出错
if(condition){
function sayHi(){
console.log('hi');
}
} else {
function sayHi(){
console.log('hello');
}
}
编译器会跳过if判断,直接把第二个函数声明覆盖第一个
换成函数表达式就可以了
// 正确
let sayHi
// 出错
if(condition){
sayHi = function(){
console.log('hi');
}
} else {
sayHi = function(){
console.log('hello');
}
}
10.12 递归
自己调用自己
function factorial(num){
if(num<=1){
return 1
} else {
return num * factorial(n-1)
// 使用 arguments.callee 可以使函数逻辑与函数名解耦
return num * arguments.callee(n-1)
}
}
在严格模式下不能使用arguments.callee,但是可以使用命名函数表达式实现
const factorial = (function f(num){
if(num<=1){
return 1
} else {
return num * factorial(n-1)
// 使用 arguments.callee 可以使函数逻辑与函数名解耦
return num * f(n-1)
}
})
10.13 尾调用优化
目前没有办法测试尾调用优化是否有用,但是浏览器都能保证实现
10.14 闭包
闭包指的是另一个函数作用域中变量的函数,通常是在嵌套代码中实现的
如下代码展示了闭包
function createComparisonFunction(propertyName){
return function(object1,object2){
let value1 = object1[propertyName]
let value2 = object2[propertyName]
//比较代码
}
}
10.15 立即调用的函数表达式
(function(){
块级作用域
})()
比如for中使用let初始化变量
for(let i=0;i<10;i++)
10.16 私有变量
任何定义在函数内部的变量和函数都可以认为是私有的,因为在块外无法访问
function MyObject(){
// 私有变量和私有函数
let privateValue = 10
function privateFunction(){
return false
}
// 特权方法
this.publicMethof = function(){
privateValue++
return privateFunction()
}
}
静态私有变量
特权方法可以通过私有作用域定义私有变量和函数实现
(function(){
// 私有变量和私有函数
let privateValue = 10
function privateFunction(){
return false
}
// 构造函数
MyObject = function(){}
// 公有和特权方法
MyObject.protorype.publicMethod = function(){
privateValue++
return privateFunction()
}
})()
使用闭包和私有变量会使作用域链边长,但是每个实例会有自己的私有属性
模块模式
单例对象需要进行某种初始化,且需要访问私有变量时使用
let singleton = (function(){
// 私有变量和私有函数
let privateValue = 10
function privateFunction(){
return false
}
// 公有/特权方法/属性
return {
publicPrototype: true,
publicMethod(){
privateValue++
return privateFunction()
}
}
})()
模块加强模式
let singleton = (function(){
// 私有变量和私有函数
let privateValue = 10
function privateFunction(){
return false
}
// 创建对象
let object = new CustomType()
// 公有/特权方法/属性
object.publicPrototype= true
object.publicMethod = function(){
privateValue++
return privateFunction()
}
//返回对象
return object
})()
可以创建变量object保存CustomType的实例