javascript 函数 -声明+实际应用
本篇介绍javascript函数的相关概念
函数声明
函数定义
函数表达式
var fn = function(){
console.log(1)
}
function + 函数名字
function fnf(){
console.log(1)
}
new Function()
传递参数 参数名称,参数字符串
var fnc = new Function('name','age','console.log(name,age)');
fnc('33',33)
函数的变量提升
- 函数表达式进行定义的函数,会提升
var + 函数名字
,而=后的作为参数进行赋值操作 - 使用
function
定义的函数,在变量提升的时候会整体进行提升;
//函数表达式
console.log('--函数表达式开始-',fn)
var fn = function(){
console.log(1)
}
console.log('--函数表达式结束-',fn)
/** 函数fun */
console.log('--function开始-',fnf)
function fnf(){
console.log(1)
}
console.log('--function结束-',fnf)
函数类型
具名函数
具名函数是有函数名字的函数,
var fnNameFn = function(){}
function fnNameFn2(){}
console.log(fnNameFn.prototype)
匿名函数
无函数名称、如立即执行函数,
(function(){ }())
箭头函数
箭头函数涉及的内容比较多,最常见的就是this指向的问题。箭头函数最常见的使用场景就是回调函数了
let arrowFun = ()=>{}
console.log(arrowFun.prototype) // undefined
箭头函数的定义格式
// 直接返回值
let arrfn = ()=> 2;
console.log(arrfn())
//传递参数进行判断
let arrfn2 = (name)=> name !='mfy';
console.log(arrfn2())
函数参数
函数的参数分为实参
和形参
- 实参 实际传入的参数
- 形参 只能使用在函数体内部的变量
function add(num1,num2){} // num1 、num2是形参
var a=1,b=3;
add(a,b) // a,b 为实参
arguments
- 普通函数是存在arguments
- 箭头函数不存在arguments
let funMfy =function(){
console.log(arguments)
}
funMfy('mfy','23')
//箭头函数
let fnnn = ()=>{
console.log(arguments)
}
fnnn('mfy',33) //报错
收集参数
let fyy = function(...args){
console.log(args)
}
fyy('mfy',32,[])
函数应用
函数的使用在js中非常非常多了,其实以下的相关介绍,无非就是在基础函数中进行变换和使用才有了下面的名此
闭包
一个函数和对其周围状态(lexical environment,词法环境)
的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript
中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
function clouser(){
let a = '666'
return function(){
console.log(a)
}
}
let bfn = clouser();
bfn();//666
扩展知识点:变量提升
作用域
闭包应用:工厂函数
立即执行函数
闭包常见问题
- 循环问题
- 变量打印问题
高阶函数
高阶函数(higher-order-function)指操作函数的函数
,一般地,有以下两种情况:
- 1、函数可以作为参数被传递
- 2、函数可以作为返回值输出
javascript
中的函数显然满足高阶函数的条件,在实际开发中,无论是将函数当作参数传递,还是让函数的执行结果返回另外一个函数,这两种情形都有很多应用场景。下面将对这两种情况进行详细介绍;
函数做为参数传递
把函数当作参数传递,代表可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个常见的应用场景就是回调函数。
回调函数是最常见的参数传递了在ajax异步请求的应用中,回调函数的使用非常频繁。想在ajax请求返回之后做一些事情,但又并不知道请求返回的确切时间时,最常见的方案就是把callback函数
当作参数传入发起ajax请求的方法中,待请求完成之后执行callback函数
;
function ajax(callback){
$.ajax( 'http://xx.com/getUserInfo?' + userId, function( data ){
//不确定什么时候返回,进行回调执行
if ( typeof callback === 'function' ){
callback( data );
}
});
}
很多框架已经异步处理的时候都使用了函数作为参数传入Vue的this.nextTick
、React 的setState
函数作为返回值
闭包
、 工厂函数
、
function People(){}
function Factort(){
var instance = null
return function(){
if(!instance)
instance = new People();
}
return instance;
}
函数柯里化
函数的柯里化(currying)
又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值.简称闭包;
demo 花费计算问题
//通用的柯里化函数
var curring = function(fn){
var args = [];
return function(){
if(arguments.length ==0){
return fn.apply(this,args);
}else{
[].push.apply(args,arguments);
}
}
}
var cost1 = (function(){
var money = 0;
return function(){
for(var i=0;i<arguments.length;i++){
money+=arguments[i]
}
return money;
}
})()
var cost3 = curring(cost1);
cost3(100);
cost3(200);
cost3(100);
console.log(cost3())
求值柯里化
传递多个参数时候
/**
* 求值柯里化
*/
var curring3= function(fn){
//获取fn外的其他参数
var args = [].slice.call(arguments,1);
return function(){
// 获取fn的所有参数
var innerArgs =[].slice.call(arguments);
// 最终参数列表重合展开
var finnalArgs = args.concat(innerArgs);
//将参数列表展开,并传入fn中
return fn.apply(null,finnalArgs)
}
}
var cost6 = (function(){
var money = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})()
var costC = curring3(cost6,300,233);
console.log(costC(44))
console.log(costC(2003,444))
反柯里化
Array.prototype
上的方法原本只能用来操作array对象。但用call和apply可以把任意对象当作this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性
有没有办法把泛化this的过程提取出来呢?反柯里化(uncurrying)就是用来解决这个问题的。反柯里化主要用于扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。
Function.prototype.unCurring=function(){
var _this = this;
return function(){
var obj = Array.prototype.shift.call(arguments);
return _this.apply(obj,arguments); //更改当前的this指向
}
}
//另一种方法实现
Function.prototype.currying = function() {
var _this = this;
return function() {
return Function.prototype.call.apply(_this, arguments);
}
}
递归函数
递归函数则是自己调用自己本身,最常见的一个就是对象的深拷贝
以及斐波那契
惰性函数
惰性函数
表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。javascript
的浏览器事件兼容
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false);
}
else if(element.attachEvent){
element.attachEvent('on' + type, fun);
}
else{
element['on' + type] = fun;
}
}
//添加事件
//第一次绑定
var ele = document.getElementById("bind-event");
addEvent('bind-event',ele,()=>{
console.log(111)
});
//第二次绑定
var ele1= document.getElementById("bind-event2");
addEvent('click',ele1,()=>{
console.log(222)
})
我们每次绑定的时候都回去判断当前是否支持,而在一个浏览器中,我们只需要进行判断一次即可;惰性函数的本质就是函数重写
,所谓惰性载入,指函数执行的分支只会发生一次,有两种实现惰性载入的方式;
- 第一种 函数的重写 进行变量绑定赋值
function addEvent(type, element, fun){
if (element.addEventListener) {
addEvent = function(type, element, fun){
element.addEventListener(type, fun, false);
}
}else if(element.attachEvent){
addEvent = function(type, element, fun){
element.attachEvent('on' + type, fun);
}
}else{
addEvent =function(type,element,fun){
element['on' + type] = fun;
}
}
return addEvent(type, element, fun)
}
在这个惰性载入的addEvent()
中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()
时,便会直接调用新赋值的函数,这样就不用再执行if语句了;
- 第二种是声明函数时就指定适当的函数。把嗅探浏览器的操作提前到代码加载的时候,在代码加载的时候就立刻进行一次判断,以便让addEvent返回一个包裹了正确逻辑的函数;
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun;
}
}
})();
纯函数
简单来说,一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数
。这么说肯定比较抽象,我们把它掰开来看:
- 返回结果只依赖于它的参数
- 函数执行过程中是没有副作用的
var a =1;
function ac(b){
return a+b
}
此时不是纯函数,因为此时函数的结果依赖的函数外部的变量a
;
const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3
此时函数依赖函数传递过来的参数,当传递的参数为1,2的时候,无论如何都不会改变函数传出的结果
一个函数的返回结果只依赖于它的参数。
?? 引用类型数据 当作参数传入的时候,如果内部更改了引用类型的数据,那么他就不是纯函数
,因为出现了副作用;
防抖和节流
这个高频点,另分出来进行解释
js函数+实际应用面试高频点
- 函数参数arguments的使用
- 箭头函数使用点
- 函数内部this指向问题
- 闭包问题
- 闭包概念
- 常见闭包问题
- 闭包产生的缺点
- 涉及作用域
- 函数柯里化(参数收集过程)
- 写一个纯函数(可能是编程中,要求写纯函数)
- 防抖和节流 (面试被问频率非常高)
- 防抖和节流使用场景
- 手写防抖和节流
参考文档
- MDN闭包讲解
- js纯函数讲解
-《javascript 高级程序设计第四版》
- 很早之前参考的一篇文章