函数基础学习


高内聚低耦合->模块的单一责任制->强的功能性和独立性
解耦合-> 将重复的代码功能抽离成单独的函数。
函数可以解耦合

定义函数

定义函数有两种:
一种是函数声明,
另一种就是函数表达式

函数声明

函数声明包括把函数体赋值给函数名称这一步骤

function functionName(arg0,arg1){
    //函数体
}
//浏览器都给函数定义了一个非标准的name属性
//通过这个属性可以访问到函数的名字
//这个属性的值永远等于function关键字后面的标识符
console.log(functionName.name)//"functionName"
  1. 关于函数声明,它最重要的特征就是函数声明提升,
  2. 在执行函数之前会先读取函数声明。
  3. 这意味着可以把函数声明放在调用它的语句后面
console.log(sayHi.name,sayHi.length,sayHi.toString())
// sayHi 0 function sayHi(){
//     alert('hi')
// }
sayHi();
function sayHi(){
    alert('hi')
}

禁止使用 var a = b = 1;声明和初始化变量

function test(){
    var a = b = 1;
    console.log(a,b) //1 1
}
console.log(b) // 1
console.log(a) // a not defined

构造函数式

//最后一个参数表示函数体,前面的参数表示函数的参数
var demo3 = new Function('a','b','console.log(a+b)')
demo3(1,2)//3
//设置返回值
var demo3 = new Function('a','b','return a+b')
var result = demo2(1,3)

  • 让字符串作为语句执行的第二种方法 eval
eval('console.log(1+2)')
  • 区别

    他们都可以执行字符串函数

eval("var e = 'e'")//eval中定义的变量e为全局变量
var demo4 = new Function('a','b','var a = 0;return a+b')
demo4(1,3)//new Function中定义的变量是函数中的变量为局部变量
  • 字符串和数字当做工厂方法去使用,不是创建对象实例,而是包装对象(类型的转换)
var str = new String(100)
var str2 = String(100)
console.log(str,str2)

var num1 = new Number(100)
var num2 = Number(100)
console.log(num1,num2)
//工作中经常使用String与Number工厂方法实现显性的数据类型转换

var b1 = new Boolean(100)
var b2 = Boolean(100)//包装 显性转换
console.log(b1,b2)

//创建正则
var reg1 = /^a/  //正则字面量
var reg2 = new RegExp('^a','i')//参数1 正则内容, 参数2 正则修饰符
//工厂方法和构造函数是一样的
var reg3 = RegExp('^a','i')
console.log(reg2,reg3)

var err = new Error('这是一个错误')
console.log(err)
console.log('abc')
// Error也是一个安全类
console.log(Error('这是一个错误'))
//抛出错误 红色字体 程序会被终止
throw err 


//日期 安全类
var d = new Date()
var d2 = Date()
console.log(d,d2)

函数表达式

命名函数表达式

将一个函数赋值给一个变量

//有标识符的叫命名函数表达式
var functionName = function foo(arg0, arg1, arg2) {
    //函数体
};
functionName.name == "foo"
//foo 只能在函数体内访问

eg

// test()
var test = function testFun(){
    var a = 1,b = 2;
    console.log(a,b);
    console.log(testFun.name,1) //testFun 1
    console.log(test.name,2) //testFun 2
}
console.log(test.name);//test
test() //1 2
console.log(testFun) //testFun is not defined

匿名函数表达式

去掉function后面的函数名称,也叫函数字面量表达式

函数表达式有几种不同的语法形式,最常见的如下

var functionName = function(arg0,arg1){
    //函数体
}

这种形式看起来像是最常规的变量赋值语句,
即创建一个函数并将它赋值给变量functionName.
这种情况下创建的函数叫匿名函数,也叫拉姆达函数。
匿名函数的name属性是空字符串

函数表达式与其他表达式一样,在使用前必须赋值
函数表达式无论是否有名字都没有函数提升

sasyHi()//报错 
sayHi(){
    alert('hi')
}

函数声明与函数表达式之间的区别

理解函数提升的关键就是理解函数声明与函数表达式之间的区别

//不要这样做
if(condition){
    function sayHi(){
        alert('hi')
    }
}else{
     function sayHi(){
        alert('Yo')
    }
}

表面上看,以上代码在condition=true时,使用sayHi()的定义;否则就使用另一个定义。
实际上在ES中这是一个无效语法。JS引擎会尝试修正错误,将其转换为合理的状态。
但每个浏览器的JS引擎修正错误的做法都不一致。
大多数浏览器都返回第二个声明,忽略condition。

不过如果使用函数表达式那就没有什么问题了

var sayHi
if(condition){
    sayHi = function(){
        alert('hi')
    }
}else{
    sayHi = function(){
        alert('Yo')
    }
}

函数的参数和返回值

arguments、functionName.length

 //将一个函数字面量赋值给test->匿名函数表达式
 var test = function(){
     console.log(1)
 }
 //==========================================
 //函数声明
 function test(a,b){
    console.log(a,b)
    console.log(test.length)//查看形参的个数
    console.log(arguments)  //查看调用函数的实参

  }
  var a= Number(window.prompt('a'))
  var b= Number(window.prompt('b'))
  test(a,b)   //实参和形参的调用

实参和形参的区别和求和问题

  1. 函数中使用arguments查看调用函数的实参
  2. 函数中使用functionName.length查看形参的个数
  3. 实参和形参数量可以不相等
  4. 实参比形参少,未被赋值的形参在函数中为undefined
  5. 实参比实参多,没有影响
  • 实参arguments求和(面试题)
//实参求和:一个函数被调用时累加它的实参值
function sum(){
    var a=0;
    for(var i=0;i<arguments.length;i++){
        a+=arguments[i]
    }
    console.log(a)
}
sum(1,2,3)

实参和形参的关系

必记

  1. 如果有传入对应形参,则函数内部可以通过改变它形参并且影响他的实参arguments的值,改变对应实参其对应形参也会改变,此时他们是一一映射的,但存在内存中不同的位置
  2. 如果没有传入对应形参,函数内部对应的实参为undefined,此时函数内部改变形参不影响实参arguments的值,改变对应实参其对应形参也不会改变,此时没有一一映射的关系
function test(a,b){
    a=3;
    b=6
    console.log(arguments[0])//3 函数可以改变实参的值
    console.log(arguments[1])//undefined
    arguments[0] = 9;
    arguments[1] = 9
    console.log(a,arguments[0]) //9 9
    console.log(b,arguments[1]) //6 9
}
test(1)

arguments.callee、递归、命名函数表达式的应用

arguments.callee 属性包含当前正在执行的函数。

callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。
这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。

//==========================
//this 函数内部的this
/**
全局this->window
预编译函数this->window
apply/call函数改变this指向
构造函数的this指向实例化对象

**/
function test(a,b,c){
    console.log(arguments.callee.length)//3
    console.log(test.length)//形参的个数
    //arguments.callee 是当前正在执行的函数实例
    //arguments.callee.length = test.length  
    console.log(arguments.length)//实参的个数
}
test(1,2)

  • 递归和阶乘

递归函数是在一个函数中通过名字调用自身的情况下构成的

  1. 计算阶乘(数字太大会有性能问题)

    function fact(n){
        if(n<=1){
            return 1
        }
        return n * fact(n-1)
    }
    console.log(fact(5))
    

    这是一个经典的递归阶乘函数。
    虽然这个函数表面上看起来没有什么问题,但下面的代码却可能导致它出错

    function fact(n){
        if(n<=1){
            return 1
        }
        return n * fact(n-1)
    }
    //先把fact()递归函数保存在变量anotherFact中
    var anotherFact = fact
    //然后将fact变量设置为null
    fact = null
    //结果指向原始递归函数的引用只剩下anotherFact
    //但在函数内部返回时又需要调用fact() 
    //而fact已经是null了所以会报错
    fact(5)
    
    //在这种情况下使用arguments.callee 可以解决这个问题
    //arguments.callee是一个指向当前正在执行函数的指针,因此可以用它来实现对函数的递归调用
    
    function fact(n){
        if(n<=1){
            return 1
        }
        return n * arguments.callee(n-1)
    }
    
    
  2. 和的累加

    function sum(n){
        if(n<=1){
            return 1
        }
        return n +sum(n-1)
    }
    var res = sum(3)//6
    
    //如果是匿名函数,如何递归 。
    //-->当前函数arguments.callee
    var sum = (function(n){
        if(n<=1){
            return 1
        }
        return n +arguments.callee(n-1)
    })(3)
    

但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误,
不过可以使用命名函数表达式达成相同的结果

var fact = (function f(n){
        if(n<=1){
            return 1
        }
        return n * f(n-1)
    })

命名函数表达式的另一种写法

var fact = function f(n){
    if(n<=1){
        return 1
    }
    return n * f(n-1)
    //f只在函数内部可见,外部不可用
}
fact.name //"f"

上面代码创建了一个为f的命名函数,然后将它赋值给变量fact。
即使把函数赋值给了另一个变量,函数的名字f任然有效。所以递归照样能正确调用。

警告:在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 通过要么给函数表达式一个名字,要么使用一个函数声明.

  • 为什么 arguments.callee 从ES5严格模式中删除了?
    1. 早期版本的 JavaScript不允许使用命名函数表达式,只能使用匿名函数表达式,
      出于这样的原因, 你不能创建一个递归函数表达式。
    2. 为了解决这个问题, arguments.callee 添加进来了。
    3. arguments.callee的缺点:
      要原因是递归调用会获取到一个不同的 this 值,例如:
      var global = this;
      var sillyFunction = function (recursed) {
          if (!recursed) { return arguments.callee(true); }
          if (this !== global) {
              alert("This is: " + this);
          } else {
              alert("This is the global");
          }
      }
      sillyFunction();
      
    4. ECMAScript 3 通过允许命名函数表达式解决这些问题。例如:
    [1,2,3,4,5].map(function factorial (n) {
        return !(n > 1) ? 1 : factorial(n-1)*n;
    });
    

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments/callee

caller

  • 返回调用指定函数的函数.

该特性是非标准的,请尽量不要在生产环境中使用它

如果一个函数f是在全局作用域内被调用的,则f.caller为null,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数.

该属性的常用形式arguments.callee.caller替代了被废弃的 arguments.caller.

  • 例子: 检测一个函数的caller属性的值
    下例用来得出一个函数是被谁调用的.
function myFunc() {
   if (myFunc.caller == null) {
      return ("该函数在全局作用域内被调用!");
   } else
      return ("调用我的是函数是" + myFunc.caller);
}

ES6对arguments的弱化

ES6中对arguments的弱化

  1. 函数形参中但凡有一个参数有默认值,arguments和实参没有映射关系
  2. 函数形参中如果使用rest参数,也没有映射关系
  3. 函数形参中如果使用参数结构的形式,也没有映射关系
  4. 严格模式下,也没有映射关系
  5. arguments不是箭头函数中的内置变量
function test(a = 100){
    arguments[0] = 10;
    console.log(a,arguments[0]);//1 10
}
test(1)

function test2(a = 100){
    a = 1000;
    console.log(a,arguments[0]);//1000 1
}
test2(1)
  • arguments不是箭头函数中的内置变量
    arguments是类数组,我们在里面定义了一些caller,callee等方法
    现在caller,callee等方法已经不被需要了
    而且开发中常常需要将arguments转换为数组,调用数组的默认方法。
    所以ES6中的arguments正慢慢弱化其使用
var test = (...args)=>{
    // console.log(arguments)
    console.log(args);
    console.log(Array.isArray(args))
}
  • arguments性能杀手

  • 最佳实践应该使用es6的语法rest参数来替代arguments

  • 怎么样安全的使用 arguments ?

    • arguments.length
    • arguments[i] 这里 i 必须一直是 arguments 的整数索引, 并且不能超出边界
    • 除了 .length 和 [i], 永远不要直接使用 arguments
    • 严格地说 x.apply(y, arguments) 是可以的, 但其他的都不行, 比如 .slice. Function#apply 比较特殊
function test(){
    // slice 用在arguments上会阻止js引擎做一些特定的优化
    var argArr = [].slice.call(arguments);
    console.log(argArr);
}

解决:

// 处理方法则是使用内联的代码创建数组:
function doesntLeakArguments() {
    //.length 只是一个整数,不会泄露
    // arguments 对象本身
    var args = new Array(arguments.length);
    for(var i = 0; i < args.length; ++i) {
        //i 始终是 arguments 对象的有效索引
        args[i] = arguments[i];
    }
    return args;
}
function anotherNotLeakingExample() {
    var i = arguments.length;
    var args = [];
    while (i--) args[i] = arguments[i];
    return args
}
function anotherNotLeakingExample2() {
    var args = arguments.length ==1 ? arguments[0] 
            : Array.apply(null,arguments);
    return args
}

参考 JavaScript 性能优化杀手、
MDN
managing-arguments、
原因stackoverflow

函数中的return和表单验证

function test(){
    console.log('我正在执行')
    console.log('我执行完了结束这个函数')
    //每个函数都会默认在最后添加一条return用于结束函数
    //return 也可以返回一个任何类型的数据 return 'hello'
    // return 'hello' 
}
console.log(test() )

//表单验证
function test(name){
    if(!name){
        return '您没有填写姓名!'
    }
    return name
}

function test(name){
    return name || '您没有填写姓名!'
    //undefined null NaN  0  空字符串 都是false
}

参数默认值的问题

  1. 当传入的实参是undefined,而形参默认值不是undefined时,形参生效
  2. ES5不支持形参上写默认值,ES6才开始支持
function test(a=1,b){
    console.log(a)
    console.log(b)
}
//如何只给b传入实参,让a为默认值
//给a传入undefined
test(undefined,2) //1,2
//---------------------
function test(a=undefined,b){
    console.log(a)
    console.log(b)
}
test(1,2) //1,2
//----------------------
//实参 和 形参的映射关系
//实参和形参是一一对应,且是放在不同位置的两个内存空间
//当传入的实参是undefined,而形参默认值不是undefined时,形参生效
//ES5不支持形参上写默认值,ES6才开始支持
  • 参数默认值的最佳实践
  1. 常见最佳写法
function test(a,b){
    var a = arguments[0] || 1
    var b = arguments[1] || 2
    console.log(a)
    console.log(b)
}
//如何只给b传入实参,让a为默认值
//给a传入undefined
test(undefined,2) //1,2
  1. 另一种写法
function test(a,b){
    var a,b
    if(typeof(arguments[0]) === 'undefined'){
        a=1
    }else{
        a = arguments[0]
    }
    if(typeof(arguments[1]) === 'undefined'){
        b=2
    }else{
        b = arguments[1]
    }
    //也可以使用三目运算
    console.log(a)
    console.log(b)
}

函数参数调用和call、apply、bind

function test(param){
    console.log("test method",param)
}

var obj ={}

//直接调用
test('hi')

//通过其他对象调用
//obj.test() 不能直接调用
//可以让一个函数成为指定任意对象的方法进行调用
test.call(obj,'hi')
test.apply(obj,['hi'])
test.bind(obj)('hi')


//new调用
new test()
  1. call()、apply()、bind() 都是用来重定义 this 这个对象的!他们都是Function的原型方法
  2. 以上除了了 bind 方法后面多了个 () 外 ,结果返回都一致!
    由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。
  3. call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:
    • call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'param1', ... ,'string' )
    • apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['param1', ..., 'string' ])
    • bind 除了返回是函数以外,它的参数和 call 一样。
    • 当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!
  • 调用函数时传递变量时,是值传递还是引用传递?
  1. 理解1
    都是值传递(基本、地址值)
  2. 理解2
    可能是值传递,也可能是引用传递

函数中的this

任何函数本质上都是通过对象来调用的,如果没有显式指定则这个对象是window
所有函数内部都有一个变量this,
它的值是调用函数的当前对象

test() this 指window
new test() this 指当前新创建的对象

//全局变量
b=2
function test1(){
    //局部变量
    a=1
    console.log(b)
    function test2(){
        //局部变量
        var c = 3
        console.log(b)
    }
    test2()
    console.log(c)
}
test1()
console.log(a)        //a is not defined
console.log(typeof a) //"undefined"
console.log(typeof(a)) //"undefined"

JS引擎预编译流程

<html>
    <body>
        <script>
            console.log(1)
            console.log(a)
        </script>
    </body>
</html>

直接打开页面报错a is not defined,没有打印1,这是js预编译时报的错误

  • 预编译探索
console.log(a)  //undefined
var a =1        //如果不写var a, 报错 is not defined
// 总结:var变量的声明提升

//全局变量的隐式声明(不写var 默认为全局变量)
//全局对象都是window的属性
var a =1//=> window.a=1 => a=1
console.log(a)

function test(){
    var a= b =1 
    // b 前面没有var 会被提升到全局变量,所有函数外部可以访问b,则a没有提升,还是在函数内部
}
test()
console.log(a) //a 不能访问 报错 is not defined
console.log(window.a) // 没报错 undefined
//直接访问对象里面不存在的属性为undefined
//直接访问未定义的变量则报错is not defined
console.log(b) //1

AO(activation object)活跃对象->函数上下文

AO = {
1. 形参变量声明和函数体内变量声明
2. 实参赋值给形参
3. 寻找函数声明赋值为函数体
4. 执行此函数体内代码
}

//函数声明
function test(a){
    console.log(a) //ƒ a(){}
    var a =1
    console.log(a) //1
    function a(){}
    console.log(a) //1
    var b = function(){}
    console.log(b) //ƒ (){} 匿名函数
    function d(){}
}
test(2)
//当test(2)执行时
//函数上下文 创建AO活跃对象(activation object)
/**
AO = {
  1.  a:undefined,(形参变量声明)
      b:undefined,(函数体内变量声明, var b = fun…)
  2.  a=>2(实参赋值给形参)
  3.  a=>fun… ,d=>fun…(寻找函数声明赋值为函数体)
  4.  执行此函数体内代码
}
**/

function test(a,b){
    console.log(a)//1
    c=0
    var c
    a=5
    b=6
    console.log(b)//6
    function b(){}
    function d(){}
    console.log(b) //6
}
test(1)
/**
AO = {
  1.  a,b:undefined,(形参变量声明)
      c:undefined,(函数体内变量声明)
  2.  a=>1(实参赋值给形参)b=>undefined
  3.  b=>fun… ,d=>fun…(寻找函数声明赋值为函数体)
  4.  执行此函数体内代码
}
**/
  • AO.3是函数声明提升,所以它优先级比较高会覆盖var同名变量提升
  • AO.2实参赋值给形参,也包含参数默认值的处理
function test(a){
    console.log(a) //ƒ a(){}
    var a =1
    console.log(a) //1
    function a(){}
}
test(2)

// =========
function test(a = 3){
    console.log(a) //3
    var a =1
    console.log(a) //1
}
test()

GO(global object)全局上下文

js中,GO===window
GO = {
1. 找变量但不赋值
2. 找函数声明赋值为函数体
3. 执行代码
}

  1. 在函数体中如果直接写c=1,那么c会被提升到GO的变量中
  2. 预编译不会执行if语句,所以if语句块中定义的变量也会被提升
var a =1
function a(){
    console.log(2)
}
console.log(a) //1
//GO global object 全局上下文
/**
GO===window
GO = {
    1. 找变量但不赋值
    2. 找函数声明赋值为函数体
    3. 执行代码
}
**/

function test(){
    var a=b=1;
    console.log(b)
}
test()// 函数执行后b被挂载到全局变量中

//以上预编译不管是否在if内
if(true){
    var a = 3
}else{
    var a = 4
}
var a
if(typeof(a)){ //typeof(a) 为 "undefined"字符串,字符串为true 
    console.log(123)
}
console.log(a)//3

作用域与作用域链

我们以及了解了js引擎的预编译以及AO和GO
作用域和作用域链式保存AO和GO的容器

变量提升和函数提升

  1. 变量声明提升
    通过var定义的变量,在定义语句之前就可以访问到。值为:undefined
  2. 函数声明提升,通过function声明的函数在之前就可以直接调用。值为:函数定义(对象)
  3. 问题:变量提升和函数提升是怎么产生的
var a = 3
function fn(){
    console.log(a) // undefined
    var a = 4 
}
fn()

console.log(b) //undefined 变量提升
var b = 3

fn2() //可调用  函数提升 正常执行
function fn2(){
    console.log('fn2()')
}

/**
var fn3 = function(){
    console.log('fn3()')
}
只有通过function声明直接定义的函数才会有函数提升
**/
//全局变量函数都会挂载到window上

IF块作用域的变量提升和函数提升

  • var变量:无论if true 还是 false var 变量正常提升
    console.log(a,b)// undefined undefined
    if(false){
        var b = 0;
    }
    if(true){
        var a = 1;
    }
    console.log(a,b) //1 undefined
    
  • if内函数提升同var变量,条件式函数声明丧失了函数声明提升的特性。
    console.log(a,b)// undefined undefined
    if(false){
        function b(){};
    }
    if(true){
        function a(){};
    }
    console.log(a,b) //ƒ a(){} undefined
    
  • 块级作用域下的函数声明=>函数提升同var声明,执行同let 函数赋值
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions#非严格模式下的块级函数
    console.log(a)//undefined
    if(true){
        function a(){};
        a = 1;
        console.log(a) //1 
    }
    console.log(a) //ƒ a(){}
    

执行上下文、执行上下文栈

  • 执行上下文
  1. 代码分类
    • 全局代码
    • 函数(局部)代码
  2. 全局执行上下文
    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var定义的全局变量==>undefined,添加为window的属性
      • function声明的全局函数==>赋值(fun),添加为window的方法
      • this==> 赋值(window)
    • 开始执行全局代码
  3. 函数执行上下文
    • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象,存在于栈中
    • 对局部数据进行预处理
      • 形参变量==>赋值(实参)==>添加为执行上下文的属性
      • arguments==>赋值(实参列表),添加为执行上下文的属性
      • var定义的局部变量==>undefined,添加为执行上下文的属性
      • function声明的函数==>赋值(fun),添加为执行上下文的属性
      • this==>赋值(调用函数的对象)
    • 开始执行函数体代码
//函数执行上下文
function fn(a1){
    console.log(a1)//2
    console.log(a2)//undefined
    a3() //a3
    console.log(this) //window
    console.log(arguments)//伪数组[2,3]
    var a2 =3
    function a3(){
        console.log('a3()')
    }
}
fn(2,3)// 这边通过window调用的函数,所以函数里this为window
  • 执行上下文栈
  1. 在全局代码执行前,js引擎将会创建一个栈来存储管理所有的执行上下文对象GO
  2. 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
  3. 在函数执行上下文栈创建后(管理AO),将其添加到栈中(压栈)(此时函数执形上下文在栈顶)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有代码执行完后,栈中只剩下window(GO)

作用域

  1. 分类
    • 全局作用域
    • 函数作用域
    • 没有块作用域(ES6有了)
  2. 作用:隔离变量,不同作用域下同名变量不会冲突
  3. 作用域与执行上下文
    • 区别1
      • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
      • 全局执行上下文环境是在全局作用域确定后,js代码马上执行前创建的
      • 函数执行上下文环境是在调用函数时,函数体代码执行之前创建的
    • 区别2
      • 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
      • 上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被释放
    • 联系
      • 上下文环境是从属于所在的作用域
      • 全局上下文环境==>全局作用域
      • 函数上下文环境==>对应的函数作用域
  • 面试:作用域在函数定义时就已经确定了,而不是在函数调用时
var funOuter = function(){
    console.log(this.name)
}
var name = 222
var b = {
    name:333,
    say:function(fun){
        console.log(this.name)//333
        fun() //这里执行的是funOuter函数
        // 对于this,谁调用方法,this指向谁,所以传进来的函数的this没有被改变
    }
}
b.say(funOuter) //222 
// 为啥是222
// 函数是对象,把函数当做参数传入的是函数的引用,而函数的作用域在定义的时候就已经确定了,不管你在哪儿调用,它的作用域不会改变

  • 面试题2
var marty = {
    name : 'marty',
    printName:function(){
        console.log(this.name)
    }
}
var test1 ={
    name:'test1'
}
var test2 = {
    name:'test2'
}
var test3 = {
    name:'test3'
}

test3.printName = marty.printName;
marty.printName.call(test1)
marty.printName.apply(test2)
marty.printName()
test3.printName();
// VM405:4 test1
// VM405:4 test2
// VM405:4 marty
// VM405:4 test3

  • 面试题3
function Foo(){
    // 这里声明的getName是全局变量
    getName = function(){
        console.log(1)
    }
    return this;
}

// 相当于对象中的一个属性
Foo.getName = function(){
    console.log(2)
}
Foo.prototype.getName = function(){
    console.log(3)
}
var getName = function(){
    console.log(4)
}
function getName(){
    console.log(5)
}
Foo.getName() //2
getName() // 4
Foo().getName() //1
new Foo.getName(); //2 点运算符比new高
new Foo().getName();//3 new Foo()比点运算符高
new new Foo().getName() //3 new Foo().getName()

作用域链

  1. 理解
    • 多个上下级关系的作用域形成的链,他的方向是从下向上的(从内到外)
    • 查找变量时就是沿着作用域链来查找的
  2. 变量的查找规则
    • 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
    • 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
    • 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
  • 面试题1
var x =10;
function fn(){ 
    console.log(x) 
    //无论谁调用这个函数,在这个函数中如果找不到x,它向上一级(全局作用域查找)
    //作用域是静态的,只要确定好了就不好再变化,不管你怎么调用
}
function show(f){
    var x = 20;
    f();
}
show(fn) //10
  • 面试题2
var fn = function(){
    console.log(fn)
}
fn() //输出fn这个函数

var obj = {
    fn2:function(){
        console.log(fn2) //当前函数作用域没找到fn2 ,向上全局作用域找,找不到报错
    }
}
obj.fn2()//Uncaught ReferenceError: fn2 is not defined

var obj = {
    fn2:function(){
        console.log(this.fn2) //如果要找到内部的fn2,前面应该加this
    }
}
obj.fn2()

AO、GO

我们学习AO GO 是为了解决作用域链相关所产生的一切问题
AO=>function 独立内存空间
js对象有些属性是我们无法访问的,这是js引擎内部固有的隐式属性

  • [[scope]]
  1. 函数创建时生成的其函数对象内部的隐式属性[[scope]]
  2. 当函数被定义时,全局执行上下文生成[[scope]]属性,它保存的是该函数的作用域链,作用域链的第0位存储全局执行上下文GO
  3. 当函数执行的时候,函数执行上下文生成AO,压入作用域链的最顶端。
  4. 函数执行结束会销毁AO,AO是一个即时的存储容器,每次调用函数都会产生新的AO
function a(){
    function b(){
        var b=2
    }
    var a =1
    b()
}
var c=3
a()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQD17mTP-1652100289770)(https://blog.whaleluo.space/assets/imgs/js/scope1.png)]
每一个函数在定义的时候就生成了GO存放在作用域链
也可以说全局执行的前一刻就生成了GO
avatar
每一个函数在执行的前一刻生成了AO
avatar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DVnXgsK-1652100289772)(https://blog.whaleluo.space/assets/imgs/js/scope4.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tj1GWnUY-1652100289772)(https://blog.whaleluo.space/assets/imgs/js/scope5.png)]
avatar

闭包

闭包的定义,优缺点,作用

  1. 当test2函数被定义的时候,此时它的作用域链和test1的作用域链相同
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4L7N33yp-1652100289775)(https://blog.whaleluo.space/assets/imgs/js/closepack1.png)]
  2. 当test2执行的前一刻生成自己的AO,压入到自己的作用链顶端
    avatar

avatar

1. 当内部函数被返回到外部并保存时一定会产生闭包
2. 闭包会使原来的作用域链不释放
3. 过度的闭包会造成内存泄露或加载过慢

  • 闭包可以用作数据缓存
function test(){
    var n =100
    function add(){
        console.log(++n)
    }
    function reduce(){
        console.log(--n)
    }
    return [add,reduce] //return 以数组的形式返回多个变量
}

var arr = test()
arr[0]()
arr[0]()
arr[1]()
// 闭包可以用作数据缓存

闭包与循环变量

但函数被第一次调用的时候会创建一个执行环境和相应的作用域,
并把作用域压入一个特殊的内部属性[[Scope]]。
然后用this,arguments和其他命名参数的值来初始化函数的活动对象AO(activation object)。
但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位……直至作为作用域链终点的全局执行环境。

由于闭包会携带它的函数的作用域,因此会比其他函数占用更多的内存。

作用域链的这种配置机制引出另一个问题。
即闭包只能取得包含函数中任何变量的最后一个值

//--闭包经典面试题--
function test(){
    var arr=[]
    for(var i =0;i<10;i++){
        arr[i]=function(){
            console.log(i+' ')
        }
    }
    return arr
}
//执行arr里面的函数,应该打印出0-9 却打印出10个10
var myArr = test()
for(var j=0;j<myArr.length;j++){
    myArr[j]()
}

立即执行函数解决循环中的闭包

//---解析---
function test(){
    var arr=[]
    var i =0
    for(;i<10;){
        arr[i]=function(){
            console.log(i+' ')
        }
    }
    i++
    return arr
}
//---使用立即执行函数解决1---
function test(){
    //var arr=[]
    for(var i =0;i<10;i++){
        (function(){
            console.log(i+' ')
        }())
    }
    //return arr
}
//---使用立即执行函数解决2---
function test(){
    var arr=[]
    for(var i =0;i<10;i++){
        (function(j){
            arr[j]=function(){
                console.log(j+' ')
            }
        })(i)
       
    }
    return arr
}
var myArr = test()
for(var j=0;j<myArr.length;j++){
    myArr[j]()
}

//闭包面试题2
//需求: 点击某个按钮 提示“点击的是第n个按钮”
var btns = document.getElementsByTagName('button')
//遍历加监听
for(var i=0;i<btns.length;i++>){
    //btns.length btns是一个伪数组,每次执行btns.length都会计算一次,不是一个固定的值
    var obj= btns[i]
}
//解决以上问题,提高性能
for(var i=0,length=btns.length;i<length;i++>){
    var btn= btns[i]
    btn.onclick = function(){
        alert('第'+(i+1))
    }
}
console.log(i)
//问题 循环中只出现了一个i。i是全局变量。所以 alert('第'+(i+1))总是同样的值
//解决 将btn所对应的下标保存在btn上
for(var i=0,length=btns.length;i<length;i++>){
    var btn= btns[i]
    btn.index = i
    btn.onclick = function(){
        alert('第'+(this.index+1))
    }
}

//另一种写法 利用立即执行函数来实现
for(var i=0,length=btns.length;i<length;i++>){
    (function(i){
        var btn= btns[i]
        btn.onclick = function(){
            alert('第'+(i+1))
        }
    })(i)
}

闭包总结

  1. 如何产生闭包?
    当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时就产生了闭包

  2. 闭包到底是什么?

    • 使用Chrome调试查看 closure
    • 理解一:闭包是嵌套的内部函数
    • 理解二:包含被引用变量(函数)的对象
    • 注意:闭包存在于嵌套的内部函数中
  3. 产生闭包的条件?

    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
  4. 常见的闭包

    • 将函数作为另一个函数的返回值
    • 将函数作为实参传递给另一个函数使用
//将函数作为另一个函数的返回值
function fn1(){
    //执行到此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
    var c= 3
    c++
    console.log(c + 'in out inner fun')
    var a = 2
    function fn2(){
        a++
        console.log(a)

        console.log(c + 'is used in inner fun') 
        //这里的c永远都是4,说明 fn2外面的语句永远都只执行一次,变量c不会销毁
    }
    return fn2
}
var f = fn1()
//重要
//把fn1(实际上是返回的fn2的地址值)赋值给f,这样函数执行完后,fn2因为有被引用所以它不会销毁
//如果直接执行fn1(),那样它永远都是3,因为没有任何引用,执行完就被销毁了
f()//3
f()//4
//f=null // 闭包死亡(包含闭包的函数对象成为垃圾对象)
//将函数作为实参传递给另一个函数使用
function showDelay(msg,time){
    setTimeout(function(){
        alert(msg)
    },time)
}
showDelay('a',2000)

  1. 闭包的作用
    • 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
    • 让函数外部可以操作到函数内部的数据

问题:

  • 函数执行完后,函数内部声明的局部变量是否还存在?闭包环境下才存在
  • 在函数外部能直接访问函数内部的局部变量吗? 不能,但是通过闭包可以操作
  1. 闭包的生命周期

    • 产生:在嵌套的内部函数定义执行完就产生了(不是在调用)
    • 死亡:在嵌套的内部函数成为垃圾对象时
  2. 闭包的应用;定义js模块
    具有特定功能并向外暴露特定方法

    function myModule(){
        //私有数据
        var msg = 'Hello' 
        //操作数据的函数
        function doSomething(){
            console.log('doSomething() '+ msg.toUpperCase())
        }
         function doOtherthing(){
            console.log('doOtherthing() '+ msg.toLowerCase())
        }
        //向外暴露
        return {doSomething,doOtherthing}
    }
    

    使用立即执行函数的写法,更方便

      (function(){
         //私有数据
         var msg = 'Hello' 
         //操作数据的函数
         function doSomething(){
             console.log('doSomething() '+ msg.toUpperCase())
         }
         function doOtherthing(){
             console.log('doOtherthing() '+ msg.toLowerCase())
         }
         window.myMoudle2 = {doSomething,doOtherthing}
      })()
       
    

    下面的这种写法便于代码压缩,代码中所有的window可以被压缩为w单个字符

      (function(window){
         //私有数据
         var msg = 'Hello' 
         //操作数据的函数
         function doSomething(){
             console.log('doSomething() '+ msg.toUpperCase())
         }
         function doOtherthing(){
             console.log('doOtherthing() '+ msg.toLowerCase())
         }
         window.myMoudle2 = {doSomething,doOtherthing}
      })(window)
       
    
  3. 闭包的缺点

    • 函数执行后,函数内部的局部变量没有被释放,占用内存时间边长
    • 容易造成内存泄露
    • 解决:能不用闭包就不用闭包,及时释放
    function fn1(){
        var arr = new Array[100000]
        function fn2(){
            console.log(arr.length)
        }
        return fn2
    }
    var f= fn1()
    f()
    
    f=null //解决 ,回收闭包    
    
    

    内存溢出
    当程序运行所需要的内存超过了剩余的内存会抛出内存溢出的错误
    内存泄露
    占用的内存没有及时释放
    内存泄露积累多了就容易导致内存溢出
    常见的内存泄露

     * 意外的全局变量
     * 没有及时清理的循环定时器setInterval或回调函数
     * 闭包
    

面试题

    var name = 'The Window'
    var object = {
        name:'My Object',
        getNameFun:function(){
            return function(){
                return this.name
            }
        }
    }
    alert(object.getNameFun()()) //The Window

    var name = 'The Window'
    var object = {
        name:'My Object',
        getNameFun:function(){
            var that = this
            return function(){
                return that.name
            }
        }
    }
    alert(object.getNameFun()()) //My Object


   function fun(n,o){
       console.log(o)
       return {
           fun:function(m){
               return fun(m,n)
           }
       }
   }
   var a = fun(0);a.fun(1);a.fun(3);
   var b = fun(0).fun(1).fun(2).fun(3);
   var c = fun(0).fun(1);c.fun(2);c.fun(3) 

关于this对象

在闭包中使用this对象可能会导致一些问题
我们知道,this对象是在运行时基于函数的执行环境绑定的:

  • 在全局函数中this=window,
  • 而函数被作为某个对象的方法调用时,this等于那个对象。

不过匿名函数的执行环境具有全局性,因此this对象通常指向window。
但由于闭包的方式不同,这一点可能不会那么明显

var name = 'window'
var object = {
    name:'object',
    getNameFunc:function(){
        return function(){
            return this.name
        }
    }
}
alert(object.getNameFunc()())//window

为什么返回的匿名函数执行时没有取得其包含作用域(或外部作用域)的this对象呢?

  • 每个函数在被调用时,其活动对象会自动取得两个特殊变量:this和arguments。
  • 内部函数在搜索这两个变量时,只会搜索到其活动对象位置,因此永远不可能直接访问到外部函数中的这两个变量。
  • 不过把外部作用域中的this对象保存在一个能够访问到的变量里,就可以让闭包访问该对象了。
var name = 'window'
var object = {
    name:'object',
    getNameFunc:function(){
        var that = this
        return function(){
            return that.name
        }
    }
}
alert(object.getNameFunc()())//object

函数返回后,that依然引用者object,
所以调用object.getNameFunc()()返回"object"

this和arguments都存在者同样的问题。
如果访问作用域中的arguments对象,必须将该对象的引用保存到另一个闭包能够访问的变量中

在几种情况下,this的值可能意外地改变

var name = 'window'
var object = {
    name:'object',
    getName:function(){
        return this.name
    }
}
object.getName() //object
(object.getName)() //object
(object.getName = object.getName)() //window

函数式编程

js编程特点:

函数式编程和面向对象编程的混编语言
弱类型

编程灵活易学不可控

面向对象与函数式编程的关系

纯函数和函数缓存池

slice 纯函数

splice 非纯函数

function cacheFn(fn){
  var cache = {};
  return function(){
    var args = JSON.stringify(arguments);
    cache[args] = cache[args] ? cache[args]+'(来自缓存池)'
                              : fn.apply(fn,arguments)
    return cache[args];
  }
}
var sum = function(){
  var res = 0
  for(var i = 0; i<arguments.length; i++){
    res +=arguments[i];
  }
  return res;
}

var test = cacheFn(sum)
console.log(test(1,2))
console.log(test(1,2))

函数组合,高阶函数,偏函数(左倾)

->饲养函数->compose

若干个纯函数,偏函数,柯理化函数组合成一个新的函数,形成数据传递,并实现一种有序执行的效果

function toUpperCase(str){
  return str.toUpperCase()
}

function exclaim(str){
  return str + '!';
}
// 高阶函数是一个接收函数作为参数或将函数作为输出返回的函数。
function compose(f,g){
  return function(x){
    return f(g(x)) //左倾函数
    // 在 js 函数中,有一种函数叫偏函数( 左倾 ),
    // 其原理是将一些函数组合封装到一个函数中,调用时可以按从右到左的顺序实现全部功能。
  }
}
var f = compose(exclaim,toUpperCase)//右到左的顺序 左倾
console.log(f('hello'))
function testArgToArr(){
  console.log(Array.prototype.slice.call(arguments))
  // splice有副作用,影响原数组
  console.log(Array.prototype.splice.call(arguments,0,arguments.length))
  console.log(arguments)//length:0

}
testArgToArr(1,2,3)
  • compose 参数比较多的情况
function toUpperCase(str){
  return str.toUpperCase()
}
function split(str){
  return str.split("")
}

function compose2(){
  var args = Array.prototype.slice.call(arguments);
  var len = args.length-1;
  console.log(args[len])

  return function(x){
    var res =args[len](x);
    console.log(res)
    while(len--){
      res = args[len](res)
    }
    return res;
  }
}
var f = compose2(split,toUpperCase)
console.log(f('hello'))
  • reduceRight
    reduceRight() 方法的功能和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前将数组中的数组项做累加。

注意: reduce() 对于空数组是不会执行回调函数的。

function toUpperCase(str){
  return str.toUpperCase()
}
function split(str){
  return str.split("")
}

function compose3(){
  var args = Array.prototype.slice.call(arguments);
  return function(x){
    return args.reduceRight((prev,cur)=>{
      return cur(prev)
    },x)
  }
}
var f = compose3(split,toUpperCase)
console.log(f('hello'))

函数组合结合率associativity

函数组合内部参数无论如何再组合结果不会受到影响

var f = compose3(split,toUpperCase)
console.log(f('hello'))
var f = compose3(split,compose3(toUpperCase))
console.log(f('hello'))//(5) ['H', 'E', 'L', 'L', 'O'] 
// 结果不变

pointfree 编程风格指南

以上compose就叫做 Pointfree:不使用所要处理的值,只合成运算过程。中文可以译作"无值"风格。

Pointfree 的本质就是使用一些通用的函数,组合出各种复杂运算。上层运算不要直接操作数据,而是通过底层函数去处理

高阶函数

一个函数接收另一个函数作为参数变量的这个函数就是高阶函数

  • map->数据处理函数
  • reduce->归纳函数
function setDate(initVal,elem){
  if(elem>3){
    initVal.push(elem)
  }
  return initVal;
}

var newArr = [1,2,3,4,5,].reduce(setDate,[])
console.log(newArr) //[4, 5]
  • 数组的扩展方法,计时器,sort,replace都是高阶函数
var test = function(a,b,fn){
  return fn(a,b)
}
var res = test(1,2,(a,b)=>a+b);
console.log(res)
  • 高阶函数如果只是为了执行另一个函数,这样是没意义的
  • 函数返回的简化
var test = function(fn){
  return doSth(function(data){
    return fn(data)
  })
}

function doSth(fn){
  fn()
}
// 上面函数嵌套函数返回时没有意义的,直接看最后的返回
var test = fn(data)

函数柯里化

// 普通的add函数
function add(x, y) {
    return x + y
}
// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3

Currying是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,
并且返回接受余下的参数而且返回结果的新函数的技术。

  • 正宗的currying以下情况都应该满足
function add(a,b,c){
  return a+b+c
}
add(1,2,3)
add(1)(2)(3)
add(1,2)(3)
add(1)(2,3)
  • 作用

    • 参数复用
    • 简化代码
    • 提高维护性
    • 功能单一化
    • 函数延迟执行
    • 功能内聚
    • 降低耦合
    • 降低代码重复
    • 提高代码适用性
  • 第一版

function add(a,b,c){
  return a+b+c
}

function curry(fn){
  var _arg = [].slice.call(arguments,1)
  return function(){
    var newArgs = _arg.concat([].slice.call(arguments))
    console.log('newargs',newArgs)
    return fn.apply(this,newArgs) //this指向没意义
  }
}

var add2=curry(add,1,2)
add2(3)

curry(add,1)(2,3) //只能两个括号 因为curry只return了一次
  • 第二版 递归
function add(a,b,c){
  return a+b+c
}

function curry(fn,len){
  var len = len || fn.length;
  var func = function(fn){
    var _arg = [].slice.call(arguments,1);
    return function(){
      var newArgs = _arg.concat([].slice.call(arguments))
      return fn.apply(this,newArgs)
    }
  }
  return function(){
    var argLen = arguments.length;
    if(argLen < len){
      var formatedArr = [fn].concat([].slice.call(arguments));
      return curry(func.apply(this,formatedArr),len-argLen)
    }else{
      return fn.apply(this,arguments)
    }
  }
}

var add2= curry(add)
add2(1)(2)(3)
add2(1,2)(3)
add2(1)(2,3)

偏函数

什么是函数的元->参数个数
有两个参数的函数->二元函数

  • 什么是偏函数
    使原函数变为元更少的函数

  • 柯里化与偏函数的区别

  • 柯里化:将一个多参数的函数转换成单个参数的函数,将n元函数转换成n个一元函数

  • 偏函数:是固定一个函数的一个或多个参数,将n元函数转换成n-x元函数

  1. 典型偏函数
function add(a,b,c){
  return a+b+c
}
var newadd = add.bind(null,1,2)
newadd(3)
  1. 自己封装
function add(a,b,c){
  return a+b+c
}

Function.prototype.partial = function(){
  var _self = this;
  var _args = [].slice.call(arguments);
  return function(){
    var newArgs = _args.concat([].slice.call(arguments));
    return _self.apply(null,newArgs)
  }
}
var newadd = add.partial(1,2)
newadd(3)

惰性函数

  1. 单例模式->懒加载
var timeStamp = null
function getTimeStamp(){
  if(timeStamp){ //每次都要进行if判断, 全局变量污染
    return timeStamp
  }else{
    timeStamp = new Date().getTime();
    return timeStamp
  }
}
console.log(getTimeStamp())
  1. 使用匿名函数
var getTimeStamp = function(){
  var timeStamp = new Date().getTime()
  return function(){
    return timeStamp
  }
})()
console.log(getTimeStamp())
  1. 函数内部改变自身->惰性函数的机制
var getTimeStamp =(function(){
  var timeStamp = new Date().getTime()
  getTimeStamp = function(){
    return timeStamp
  }
//   return timeStamp;
  return getTimeStamp()
})
console.log(getTimeStamp())

惰性加载表示函数执行的分支只会在函数第一次调用的时候执行,
在第一次调用的过程中:
该函数被覆盖为另一个按照合适的方式执行的函数,
这样任何对原函数的调用就不用再经过执行的分支了

  • 多功能函数滞后确定
function test(num){
  switch(num){
    case 1:
      test = function(){
        console.log(1)
      }
      break;
    case 2:
      test = function(){
        console.log(2)
      }
      break;
    case 3:
      test = function(){
        console.log(3)
      }
      break;
  }
  return test();
}
console.log(test(1))
console.log(test())

函数记忆,函数缓存

缓存函数
阶乘 n!=n*(n-1)
0!=1

function factorial(n){
  if(n==0 || n==1){
    return 1
  }
  return n * (factorial(n-1))
}

factorial(3)
var times = 0;
var cache = [];

function factorial(n){
  times++;
  if(cache[n]){
    return cache[n]
  }
  if(n==0 || n==1){
    cache[0] = 1
    cache[1] = 1
  }
  return cache[n] = n * (factorial(n-1))
}
  • 函数记忆
function factorial(n){
  if(n==0 || n==1){
    return 1
  }
  return cache[n] = n * (factorial(n-1))
}
function memorize(fn){
  var cache = {};
  return  function(){
    var k = [].join.call(arguments,',') //函数的参数为key
    return cache[k] = cache[k] || fn.apply(this,arguments) 
  }
}

var f = memorize(factorial)
console.log(f(100))
console.log(f(100))
  • 兔子数列 斐波那契数列 F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
    随着数列项数的增加,前一项与后一项之比越来越逼近黄金分割的数值 0.6180339887……
function fab(n){
  return n<=2 ? 1 : fab(n-1)+fab(n-2)
}
function memorize(fn){
  var cache = {};
  return  function(){
    var k = [].join.call(arguments,',') //函数的参数为key
    return cache[k] = cache[k] || fn.apply(this,arguments) 
  }
}

var f = memorize(fab)
console.log(f(8))

函数防抖,函数节流

  • 防抖
    • 在事件触发n秒后执行回调,延迟执行,如果n秒内多次触发,重新计时
    • 应用案例:input输入时验证,表单提交,鼠标事件
  • 节流
    • n秒内事件只被触发一次
  • 区别
    防抖如果一直在n秒内频繁触发就永远不会执行,而节流n秒内总会执行一次

归类函数

  • 单一归类
const user = [
  {"id":"0","name":"张三","sex":0},
  {"id":"1","name":"李四","sex":1},
  {"id":"2","name":"王五","sex":0},
  {"id":"3","name":"麻子","sex":1}
];

const sex = [
  {"id":"0","sex":'男'},
  {"id":"1","sex":'女'}
]
let cache = {};
sex.forEach((item)=>{
  let id = item.id;
  cache[id] = [];
  user.forEach((u)=>{
    let _sex = u.sex;
    if(id == _sex){
      cache[id].push(u)
    }
  })
})
console.log(cache)
  • 复合归类
const hobby = [
  {"id":1,"name":"足球"},
  {"id":2,"name":"游泳"},

];

const person =[
  {"name":"jack","hobby":"1,2"}
]
  • 归类函数封装
function sortDatas(sort,data){
  var cache={};
  return function(foreign_key,sortType){
    sort.forEach(s => {
      var _id = sort.id;
      cache[_id] = [];
      data.forEach((d)=>{
        var foreign_val = d[foreign_key];
        switch(sortType){
          case 'single':
            if(foreign_val==_id){
              cache[_id].push(d)
            }
            break;
          case 'multi':
            if(foreign_val.indexOf(_id)!=-1){
              cache[_id].push(d)
            }
            break;
          default:
            break;
        }
      })
    });
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值