函数有哪些种类?
- 普通函数
function foo(){} - 箭头函数
const foo = ()=>{} - generator
function* foo(){}Generator 函数是 ES6 提供的一种异步编程解决方案 - constructor 函数
class Foo{ constructor(){...} }类的构造函数 - async 函数
async function foo(){}Generator 函数的语法糖
什么是立即执行函数?
通过立即执行函数(IIFE)可以实现块级作用域
(function(){
//这里是块级作用域,这里面定义的变量外部是无法访问(除非自己return出去)
var a = 1;
})();
console.log(a); // Uncaught ReferenceError: a is not defined
ES6以后已经有了块级作用域的概念了,因此IIFE已经逐渐退出历史舞台了。
什么是闭包?
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数
var closure = function(){
var count = 0;
return function(){
return count ++;
}
}
const fn = closure();
console.log(fn()); // 0
console.log(fn()); // 1
console.log(fn()); // 2
fn = closure() 相当于 fn = function(){ return count ++; } , fn一直保持着对这个匿名函数的引用,而这个匿名函数又引用着count变量。因此count变量一直存在于执行上下文中,不会消失。
闭包的定义我们已经明白了,那么它可以干什么呢?
闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作
var closure = (function(){
var foo = "foo";
return {
getFoo: function(){
return foo
},
setFoo: function(newFoo){
return foo = newFoo
}
}
})();
console.log(closure.getFoo()); // foo
console.log(closure.setFoo("newFoo")); // newFoo
console.log(closure.foo); // 获取不到
什么是纯函数?
- 不会产生副作用的函数
- 相同的输入,永远会得到相同的输出
这是个纯函数,满足上面两点条件
const pureFn = function(a,b){
return a + b
}
这个不是存函数,它的副作用就是修改了外部作用域的变量。
let sum = 0;
const plus = (a,b)=>{
sum = a + b;
return sum;
}
我们构建一个项目或者说去编写一个组件,往往业务都是非常复杂的,需要定义各种变量,各种对象。如果我们定义的方法大多对外部有副作用的话,容易不可控,也就是说我们容易疏忽细节导致bug。这也就是我们需要尽可能的去编写纯函数的原因。
函数参数是对象
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // -> 26 'yck'
console.log(p2) // -> 30 'yyy'
代码解释:
- 函数传参是传递对象指针的副本,因此函数里面如果修改了对象副本的话,原对象也会跟着修改的
- 函数里面的person属性被重新赋值一个新对象,因此p2就等于新对象。
箭头函数和普通函数的区别
// 箭头函数
var f = v => v;
// 普通函数
var f = function (v) {
return v;
};
箭头函数特性:
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
const sortNumbers = (...numbers) => numbers.sort(); - 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
call 与 apply 函数
首先call和apply两个函数除了传参方式不同,功能是一样的。
改变 this 指向
call和apply最常见的用途是改变函数内部的this指向
var obj1 = {
name: 'sven'
};
var obj2 = {
name: 'anne'
};
window.name = 'window';
var getName = function(){
alert ( this.name );
};
getName(); // 输出: window
getName.call( obj1 ); // 输出: sven
getName.call( obj2 ); // 输出: anne
借用其他对象的方法
(function(){
Array.prototype.push.call( arguments, 3 );
console.log ( arguments ); // 输出[1,2,3]
})( 1, 2 );
Array.prototype.push.call( arguments, 3 ) 相当于push方法中的this被arguments对象替换了,然后返回了一个数组。其本质依然是改变了this指向。只是最后实现的结果看起来像是借用了方法。
再看一个例子:
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply( this, arguments );
};
B.prototype.getName = function(){
return this.name;
};
var b = new B( 'sven' );
console.log( b.getName() ); // 输出: 'sven'
在B构造函数中借用了A构造函数的属性。
高阶函数
高阶函数是指至少满足下列条件之一或同时满足
- 函数可以作为参数被传递
- 函数可以作为返回值输出
函数作为参数传递
常见的场景就是回调函数
var getUserInfo = function( userId, callback ){
$.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){
if ( typeof callback === 'function' ){
callback( data );
}
});
}
getUserInfo( 13157, function( data ){
alert ( data.userName );
});
函数作为返回值输出
相比把函数当作参数传递,函数当作返回值输出的应用场景也许更多,也更能体现函数式编程的巧妙。让函数继续返回一个可执行的函数,意味着运算过程是可延续的。
var isType = function( type ){
return function( obj ){
return Object.prototype.toString.call( obj ) === '[object '+ type +']';
}
};
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true
isType -> isString 然后通过 isString 就可以判断是否是字符型
函数即使参数也是返回值
单例模式的实现:
var getSingle = function ( fn ) {
var ret;
return function () {
return ret || ( ret = fn.apply( this, arguments ) );
};
};
这个高阶函数的例子,既把函数当作参数传递,又让函数执行后返回了另外一个函数。我们可以看看getSingle函数的效果:
var getScript = getSingle(function(){
return document.createElement( 'script' );
});
var script1 = getScript();
var script2 = getScript();
alert ( script1 === script2 ); // 输出:true
函数节流(throttle)
JavaScript中的函数大多数情况下都是由用户主动调用触发的,除非是函数本身的实现不合理,否则我们一般不会遇到跟性能相关的问题。但在一些少数情况下,函数的触发不是由用户直接控制的。在这些场景下,函数有可能被非常频繁地调用,而造成大的性能问题。
window.onresize事件、mousemove事件、上传进度等场景下函数会被频繁调用而影响性能。
函数节流(throttle),在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。
var throttle = function ( fn, interval ) {
var __self = fn, // 保存需要被延迟执行的函数引用
timer, // 定时器
firstTime = true; // 是否是第一次调用
return function () {
var args = arguments,
__me = this;
if ( firstTime ) { // 如果是第一次调用,不需延迟执行
__self.apply(__me, args);
return firstTime = false;
}
if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
return false;
}
timer = setTimeout(function () { // 延迟一段时间执行
clearTimeout(timer);
timer = null;
__self.apply(__me, args);
}, interval || 500 );
};
};
window.onresize = throttle(function(){
console.log( 1 );
}, 500 );
函数防抖(debounce)
函数防抖(debounce),防抖的中心思想在于,我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。
// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
var debounce = function(fn, delay) {
// 定时器
let timer = null
// 将debounce处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 每次事件被触发时,都去清除之前的旧定时器
if(timer) {
clearTimeout(timer)
}
// 设立新定时器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}