函数声明,函数表达式
声明
1.直接声明
可以放在代码的任意位置,其他地方都可以调用
2.构造函数语法 new Function
new Function(arg0, arg1, /* …, */ argN, functionBody)
- 可以动态地由字符串创建函数,需要从服务器获取代码或者动态地从模板编译函数时才会使用
- 使用 new Function 创建的函数,它的
[[Environment]]
并不指向当前的词法环境,而是指向全局环境(因此,此类函数无法访问外部变量,只能访问全局变量,也就没有闭包)
因为动态创建的函数可能无法知道里面的内容,如果能直接访问外层变量是不安全的,所以这是合理的
表达式
在执行到达时被创建,并且仅从那一刻起可用
立即执行函数表达式(IIFE,Immediately Invoked Function Expression)
函数表达式立即调用就形成了 IIFE
Tip
历史用法:以前只有 var 声明变量,可以用这种方法产生块级作用域和私有变量。现在已经基本不再需要,用块级作用域代替即可
其他 IIFE 变体
括号放里面
一元运算符
void
传参
new
匿名函数
没有名称标识符的函数,用在临时的不需要再次调用的地方
命名函数表达式(NFE,Named Function Expression)
带有名字的函数表达式
- 它允许函数在内部引用自己
- 它在函数外是不可见的
Tip
为什么要再加个名字,不直接用 sayHi ?
因为 sayHi 是外部变量,随时可能被改变,所以在 fn 内部要用 fn 表示自身
参数
JS 允许传入任意个参数而不影响调用,传入参数多了会忽略多余的,传入参数少了会赋值 undefined
arguments
- 普通函数内部有一个叫
arguments
的 类数组对象,key 是实参的位置,value 是实参值 - 改变 arguments 会影响对应的函数参数
- 有一个 key 叫 length,表示实参的个数
有一个 key 叫 callee,表示 arguments 所属的当前正在执行的函数(已弃用)
剩余参数(Rest 参数)ES6
- 用
...xxx
表示多余的参数(没有形参对应的实参) - 必须放在最后
- 它是一个真正的 Array 实例
arguments 和 Rest 的区别:
区别 1 | 区别 2 | 区别 3 | 区别 4 | |
---|---|---|---|---|
arguments | 是对象 | 所有实参 | 有额外属性 | 箭头函数不能用 |
Rest | 是数组 | 没有对应形参的实参 | 无 | 箭头函数能用 |
Rest 是用来替代 arguments 的
默认参数ES6
当值严格等于 undefined 时默认值才会生效,而且参数默认值是惰性求值的
参数尾逗号ES2017
允许函数的最后一个参数有尾逗号
Note
参数是值传递还是引用传递?
- 对基本类型:传值调用
- 对引用类型:共享调用:传给函数的参数是对象的引用的拷贝(即对象变量指针的拷贝)。如果重新给参数赋值不会影响原对象,但是改变参数对象内部的值会影响原对象
属性
函数也是对象,也有 key
name
函数的 name 属性的值会根据上下文推断:如果函数自己没有提供,那么在赋值中,会根据上下文来推测一个
length
返回函数期望的参数数量(即形参的个数)
- 不包括剩余参数(Rest)
- 只包括在第一个默认参数之前的参数
自定义属性
函数也是对象,也可以添加属性。函数上的属性通过 func.prop
来访问
this
被自动定义在函数的作用域中,是在函数 <运行时> 绑定的,取决于函数调用时的各种条件。简单说,JS 中的函数具有动态的 this,它取决于调用上下文。(this 对象引用的是函数执行的环境上下文对象)
绑定规则
绑定优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
优先级 | 绑定方式 | this |
---|---|---|
1 | new绑定 | 使用 new 操作符调用函数时,this 绑定到新创建的对象上 |
2 | 显式绑定 | 使用 apply ,call ,bind 方法,它们的第一个参数就是 this 要绑定的对象 |
3 | 隐式绑定 | 以对象的形式调用:obj.foo() ,那么 this 指向调用它的对象 obj。注意如果有多重对象的调用,则只有最后一个对象起作用 |
4 | 默认绑定 | 单独调用函数时,this 指向 undefined(严格模式),或者 全局对象 window(非严格模式) |
例外情况:箭头函数根据外层词法环境来决定 this
NOTE
隐式绑定 为什么会有隐式丢失问题?
obj.method()
调用运行的本质:
- 首先,点 ’.’ 取了属性 obj.method 的值
- 接着 () 执行了它
为确保 obj.method() 调用正常运行,JS 的点 ’.’ 返回的不是一个函数,而是一个特殊的 Reference Type 的值,他会记住调用对象的完整信息,从而设置正确的 this。
如果将调用分开,就找不到调用对象的完整信息,就会丢失 this:总结:括号里不是表达式就不影响 this,是表达式就会丢失 this
apply,call
可以设置函数体内 this 对象的值,并立即执行函数
- apply:只接受 类数组 方式传参:
f.apply(obj [, argsArray])
- call:按顺序单个传参:
f.call(obj [, arg1, arg2, ...])
,也可以用 展开语法 传入可迭代对象
f.apply(obj, args)
等价于f.call(obj, ...args)
apply 性能会好点
方法借用
有一个对象是可迭代对象又是类数组对象(如:arguments),但它并不是真正的数组。如果这时候想调用一些关于数组的方法,可以这样做:
[].xxx.call( arguments )
当然,xxx 方法的内部实现需要取的是 this 的值来操作
bind
返回一个新的绑定函数,并拥有指定的 this 和 初始参数,返回的函数可以继续接受后续的参数
let newf = f.bind(obj [, arg1, arg2, ...])
一个函数不能被重绑定(re-bound)
bind 后的新函数是另一个函数,不具有原函数中的自定义属性
偏函数(Partial Function)
通过在 bind 调用时传入部分参数,后续对新函数的调用就自动带一部分参数,只用传入后面的参数即可
在频繁传入重复相同的参数时使用
柯里化(Currying)
是一种转换,将 f(a,b,c) ⇒ f(a)(b)(c) 的形式进行调用(将多个参数的调用转换成依次调用单个参数)
柯里化要求函数必须具有固定数量的参数
箭头函数ES6
特点
箭头函数是更短的函数,通常也叫 lambda 函数
- 没有
this
,如果访问 this 对象,则会获取外层词法环境中的 this(因此不能被 new 调用当作构造函数) - 没有
arguments
对象,如果访问 arguments 对象,则会获取外层词法环境中的 arguments super
和new.target
也是和上面一样- 不能使用
yield
命令(所以不能用作 Generator 函数)
使用
如果函数里还有内部函数,根据 this 的原则,这个内部函数直接调用的 this 指向 undefined 或 window,此时可以在内层函数前面先使用 var that = this 来捕获 this:
使用箭头函数:
Warning
不适用箭头函数:
- 定义对象的方法:会导致以对象形式调用方法时 this 指向错误而不是该对象本身
- 给元素添加监听事件:会导致 this 不能指向触发事件的元素本身
总结
普通函数的 this 是运行时动态绑定的,而箭头函数的 this 是定义时静态决定的(词法作用域)
构造函数
用于创建对象的特殊函数
使用 new 操作符来调用函数时,“被调用的函数”就叫构造函数。任何函数,只要他被 new 操作符调用,那它就被视为构造函数。它并不是一个特殊的函数,不被 new 调用时它就是一个普通的函数,所以可以说实际上并不存在“构造函数”,只有对函数的“构造调用”。
执行 new 操作时会发生:
- 创建一个新的空对象
- 将这个空对象的原型(
__proto__
),指向构造函数的 prototype 属性 - 将构造函数的作用域赋值给新对象(函数内部的 this 就指向了这个空对象)
- 执行构造函数中的代码(为新对象添加属性和方法)
- 如果构造函数没有返回值或者返回原始值,那么 new 操作会自动返回这个新创建的对象;如果构造函数显式返回一个对象,那么就用显式返回的对象
new.target
在函数内部,可以使用 new.target 属性来检查它是否被使用 new 进行调用了。对于常规调用,它为 undefined,对于使用 new 的调用,则等于该函数
NOTE
class 语法是创建对象的更好办法
闭包
是指一个函数可以记住其外部变量并可以访问这些变量
JavaScript 中的函数会自动通过隐藏的 [[Environment]]
属性记住创建它们的位置,所以它们都可以访问外部变量
装饰器(decorator)
一个特殊的函数,它接受另一个函数并返回增加了某些功能的新函数
间谍装饰器
创建一个装饰器 spy(func),它应该返回一个包装器,该包装器将所有对函数的调用保存在其 calls 属性中
思路:
首先装饰器固定写法:返回一个函数,并接收原函数的所有参数,然后在合适的时候调用原函数:
返回的函数需要执行原函数,如果原函数有用到 this,在返回的函数里直接调用 fn() 根据默认绑定,this 是 undefined 而不是返回的函数被调用前面的对象。所以还需要绑定 this。如果原函数有返回值,还需要 return:
需要在原函数上面绑定一个属性,那么在返回的函数里面肯定要用到函数的名称,所以需要有 name:
最后写上业务逻辑即可:
延时装饰器
同理,但是由于 setTimeOut() 的 特性,需要用箭头函数:
如果不用箭头函数,那么就需要在外面保存 this:
防抖
一段时间后执行事件,如果在这段时间内再次触发事件,则重新计时
节流
一段时间内只运行一次,若在这段时间内重复触发,只有一次生效
1.时间戳写法:会立即调用第一次:
2.setTimeout 写法:会延时调用第一次: