9、函数知识点总结(函数概述、参数、闭包、特殊对象,js的预编译,高阶函数)

目录

9.1 函数概述

9.1.1 函数定义

9.1.2 函数调用

9.1.3 函数提升

9.1.4 自调用函数

9.1.5 函数名后的多个括号

9.2 函数参数

9.3 js的预编译

9.3.1 函数预编译

9.3.2 全局预编译

9.3.3 作用域

9.3.4 作用域链

9.4 函数闭包

9.4.1 闭包的特点

9.4.2 闭包的写法

9.4.3 闭包的用途

9.4.4 闭包的避免

9.4.5  闭包的面试

9.5 函数-特殊的对象

9.6 高阶函数

9.7 ​回调函数


函数

通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。在javascript里,函数即对象,程序可以随意操控它们。函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。

9.1 函数概述

– 一处定义,处处调用;

– 如果把函数作为一个对象的属性,则称为方法;

– 每次调用函数会产生一个this:谁调用这个函数或者方法,this就指向谁;

函数就是对象,可以给他设置属性或方法;

9.1.1 函数定义

总共有三种函数定义的方式:函数声明语句、函数表达式、内置构造函数。

函数被调用一次,就会重新执行一次函数体中的所有代码

函数声明语句

function functionName(parameters) {
    //执行的代码
}

函数声明后不会立即执行,会在我们需要的时候调用到。函数被调用一次,就会执行函数体中的所有代码

Function内置构造函数

var functionName  = new Function("parameters","执行的代码") //注意引号不可省

这种方式不推荐,无法写递归。

用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。很多时候无法获取局部变量,作用域始终是全局作用域 。所以,在 JavaScript 中,很多时候,需要避免使用 new 关键字。

函数可以嵌套在其他函数里面,也就是在函数里面可以定义函数

被嵌套的函数可以访问嵌套他们的函数的变量或参数。

函数的调用,是将实参取值后再传入函数进行执行,意思是是把a的数据值传进去,而不是传入变量名

	//函数的写法其实有两种:声明函数、定义一个函数
        //声明会提升,定义不会提升
		//1、声明函数:直接在作用域写一个函数  
		//(浏览器运行的初期就会运行此函数)
		function fn(){
		}
		// 2、定义一个函数:直接创建一个函数 把它当做数据一样 
		// (运行到此行才会运行此函数)
		var obj={
            say:function(){}
        }   //或者
        var a=function(){}


		var arr=[function fn(){},200,300]  //定义式函数
		arr[0]();
		
		(function(){})()  //定义式函数

采用一个内置对象Function来创建(动态函数)

var 函数名 = new Function("参数列表", "函数体"); 

        var a;
        var b=1;
        var f1 = new Function("a,b","var a=20;console.log(a,b)")  //20  und
        f1(10)

9.1.2 函数调用

javascript一共有4种调用模式:函数调用模式、方法调用模式、构造器调用模式和间接调用模式。   每种方式的不同在于 this 的初始化。

1、函数调用方式

a, this是指向Window的 

b, 返回值是由return语句决定的,如果没有return则表示没有返回值

2、方法调用模式

先定义一个对象,然后在对象的属性中定义方法,通过obj.say来执行方法。

a, this 是指向调用该方法的对象

b, 返回值还是由return语句决定,如果没有return表示没有返回值

3、构造器调用模式

a, this是指向构造函数的实例

b, 如果没有添加返回值的话,默认的返回值是this

c, 如果有返回值,且返回值是简单数据类型(Number,String,Boolean··)的话,最后仍回返回this

d, 如果有返回值,且返回值是复杂数据类型(对象)的话,最终返回该对象

4、间接调用模式

也称之为“apply、call调用模式” 或 “上下文调用模式”。

a, 传递的参数不同,this的指向不同,this会指向传入参数的数据类型

b, 返回值是由return决定,如果没有return表示没有返回值。

注:

1、函数的调用 最后一定会生成一个结果(数据)

        没有写返回值:调用的结果就是undefined

        写了返回值: 就是返回的数据

        函数是否必须写返回值(不是)
        函数是不是一定有返回值(是)

2、return 关键字后面紧跟的是一个表达式

return 一旦运行了 函数体后来无论还有多少代码 都不会执行了 直接函数生成结果

 不写return 或者 return紧跟着的后面不写代码的话 就是一个空表达式,会生成undfined

 return   //这个后面没有紧跟着写代码,就会生成undefined
 re;

 return(   //这样写的话就算是紧跟着有表达式
a+20) 

return a   //这样就算是a+20
+20

3、fn是否能访问自己

fn标识符的问题,因为它是函数定义时的名字,函数体内部可以直接访问
        var obj={

                say:function fn(n){ }
                fn(n-1)         //自己调用自己。上面函数是这样function fn,有名字就可以调用
                say(n-1)       //会报错。这是访问对象里面的方法
        }

4、argumrnts   和   arguments.callee( )

        //arguments:实参-->调用时传入的数据(两种写法:数据直接量、变量)
        定义式的函数可以是匿名的函数,可以没有名字
        arguments.callee  运行的函数的对象:在自己函数内部可以自调用,自己还没有写函数名,就可以:arguments.callee( )

5、对象的深拷贝 (js几个常考面试题之一)

 var o2=copy(obj)
//o2跟obj一模一样,o2自己和它内部的多余引用数据不能相等

9.1.3 函数提升

升提升(Hoisting)是 JavaScript 默认将当前作用域提到前面去的的行为;

提升应用在变量的声明与函数的声明。   使用表达式定义函数时无法提升!

因此,函数可以在声明之前调用。

变量的隐式提升:

隐式操作:把var 修饰的变量名提前声明

每一个作用域在运行时,js引擎会先把作用域内部的关键字隐式提前扫描 并声明 ,但不赋值

函数隐式提升:

变量值提升声明,函数提升的是整个函数体

定义式函数不可以提升(var a =fn () {})、声明式函数可以提升(function fn () {})

同名标识符提升

变量变量同名时:依次提升,提升会被覆盖,然后就按顺序执行

函数和函数同名时:依次提升,提升会被覆盖,然后就按顺序执行

总结---形实函运

在一个作用域的代码运行的时候  js引擎会执行代码的过程有一个执行流程

        1.先隐式提升当前作用域内部的所有形参变量和局部变量  (只是声明提升,不提升赋值)

        2.再把实参赋值给形参变量

        3.然后执行函数的隐式提前声明

        4.再按照代码顺序运行代码

9.1.4 自调用函数

(1) 函数表达式(定义式)可以 "自调用",称之为自调用表达式。如果表达式后面紧跟 () ,则会自动调用:         var fn=function () { } ();

 强制运算符()

//方式一,调用函数,得到返回值。强制运算符使函数调用执行

var test1 = (function(x,y){    alert(x+y);    return x+y; }(3,4)); ​

//方式二,调用函数,得到返回值。强制函数直接量执行再返回一个引用,引用在去调用执行

var test2 = (function(x,y){    alert(x+y);    return x+y; })(3,4);

(2) 不能自调用声明的函数!

  • 可通过添加括号,来说明它是一个函数表达式:

         (function fn ( ) { } ) ( ) ;

  • 加上void,即忽略返回值。 让 JavaScript 引擎把一个function关键字识别成函数表达式而不是函数声明

          void function fn ( ) { } ( );

语法总结:只有函数表达式才能被执行符号()执行。所以

+ function test(){}();
//正常打印出a,因为正号+将函数声明转化成了函数表达式
//还可以用负号-、叹号!等(*和/号不可以)
//其实上面“函数表达式”自调用的写法就是通过等号=将函数声明转化成了函数表达式
//总之,匿名函数自动执行,多种写法,只要不要function开头,开头加上不报错的符号就行

自调用函数特点1——函数自调用完成,函数自动被销毁。

自调用函数也可称之为”立即执行函数“,函数执行完函数空间就被释放,不能再被访问,但函数返回值可以被保存。故这种调用方法多是针对初始化功能的函数,即函数只执行一次的情况。

(function abc(){
    console.log("hello")
}())
abc();
//函数自调用执行之后(打印出字符串hello),报错abc is not defined

上例验证了,被立即执行的函数,其函数名称就会自动被忽略,均不能再通过函数名再次被调用。所以,在写立即执行函数时就没有必要写函数名称。

自调用函数特点2——逗号运算符,即返回逗号语句最后一个表达式的值。

var f=(
    function f(){
        return "1";
    },
    function g(){
        return 2;
    }
)();
console.log(typeof f);   // number   结果是2

9.1.5 函数名后的多个括号

f()意思是执行f函数,返回子函数
​
f()()执行返回的子函数,返回孙函数
​
f()()()执行返回的孙函数
​
//"函数调用"这个表达式的值始终由函数返回值决定

但注意,如果想这样执行,函数结构必须是这样,f的函数体里要return 子函数,子函数里要return 孙函数,如果没有return关键字,是不能这样连续执行的,会报错。

9.2 函数参数

函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数。

函数参数分为两类:函数显式参数(Parameters)与隐式参数(Arguments)

1、显式参数(Parameters)

function fn(a,b,c) { }           fn(1,2,3)

函数显式参数在函数定义时列出(即形参)。

函数调用未传参时,参数会默认设置为: undefined。

2、隐式参数(Arguments)

function fn( ) { }                               fn(1,2,3)

JavaScript 函数有个内置的对象 arguments 对象。

argument 对象包含了函数调用的参数数组(实参数组)。

arguments:

arguments(参数们),代表实际传入函数的参数的列表(类数组)

函数的length属性代表的是形参的个数(笔试题)

arguments对象与传入参数的映射规则:

//arguments对象与形参是相互独立的,但又存在映射规则:

//当传入参数与形参个数相等时,arguments对象与形参才是一一对应的;

//当传入参数与形参个数不等时,arguments对象与有传入参数的形参才存在映射规则。

特殊情况1:同名形参

在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。

function add(x,x,x){
    return x;
}
console.log( add(1,2,3) );  //3
​//在严格模式下,出现同名形参会抛出语法错误

特殊情况2:参数个数

当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined值。可能会影响程序的执行逻辑,但可以解决:设计函数时提前预设。

当实参多于形参,则只使用有效的实参,多出部分没影响。多的会保存在arguments里面,没有被使用。



9.3 js的预编译

js完成解释执行分为三个步骤:1.语法分析;2.预编译(全局预编译、函数预编译);3.执行语句。

9.3.1 函数预编译

第一步:检查

语法分析。符号、大括号等语法检查;

第二部:函数预编译    (创建AO对象+形实函运)

函数调用了以后,在运行代码之前,会生成一个对象:执行期上下文对象。函数每次调用都会生成对象。

变量声明提升,function函数声明整体提升;发生在函数执行的前一刻(实际过程如下):

(1) 创建AO对象--Activation Object(执行期上下文):AO{ };
(2) 找函数内部的形参变量和局部变量声明,将变量和形参名作为AO属性名,即变量提升过程,值为undefined;
(3) 将实参的值放到形参中去
(4) 在函数体里面找函数声明,值赋予函数体

9.3.2 全局预编译

”全局“即从页内js的script 的开始标签到结束标签,从页外js文件的第一行到最后一行。

全局预编译过程与函数预编译过程大致相似,只是全局上无形参、实参的概念。

1、生成一个GO对象--Global Object{},GO===window
2、变量提升:把所有的全局变量,设置为GO的属性名
3、函数提升:把所有的函数名作为GO的成员名,把函数体赋值给这个成员

全局预编译还有一步-->不同的环境中运行js代码不一样
    GO对象的成员全部浅拷贝给环境对象window
    node.js环境中没有这一步

拓展知识点----关于访问成员问题
    console.log(a)-->访问的是GO对象的成员
    console.log(window.a)  不报错,原型链没有就返回undefined

9.3.3 作用域

作用域: 指一个变量它在哪些代码范围能够被使用,这些地方就是变量的作用域

对象的大括号没有作用域的说法 ,作用域只针对于函数的大括号

在es5中 函数:

代码块内部的代码 可以访问形参变量  也可以访问外部的变量(全局) 就近优先

函数外面的代码不能直接访问函数内部的变量

全局变量(在内部和外部都可以使用)==>变量会在程序运行时 把它设置为window对象的属性

局部变量 :就是函数内部能使用 外部不能使用的变量( var,形参)

函数是一个引用数据,标识符可以在任何作用域去引用一个函数  但是:

函数运行时的作用域在哪里? 函数在生成(定义和声明)时  所在用在的作用域  

函数运行时 是在 写函数代码的地方运行代码  不是在调用代码的地方运行代码

函数每次运行的时候都会在自己声明的那个作用域重新运行

9.3.4 作用域链

执行期上下文:

函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被 "销毁"。

函数执行前一刻所产生的AO对象

[[scope]]:

每个js函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中一个。

function test() {   }
​
我们可以访问的函数属性(如:test.length/test.prototype);
我们不能访问但着实存在的函数属性(如:test.[[scope]])

[[scope]]指的是我们所说的作用域,其中存储了运行期上下文的集合。

作用域链:

[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。

查找变量:

从作用域链的顶端依次向下查找(再补充:在哪个函数里查找变量,就去哪个函数的作用域顶端去查找),最标准的说法。

 函数有属性:length      name         [[scoped]]  

 js对象有两种成员

            一种是上文成员(js语法可以直接访问的成员)

            一种是下文成员(底层语法访问的成员)      

            [[scopes]]括起来的成员名 就是下文成员

函数在定义/声明的时候 就有了[[scopes]] 里面保存了上层的AO对象

每个函数scopes数组中天生就有一个AO对象 就是这个函数的上层的AO

函数调用时会生成AO对象 AO保存在scopes对象内部的    每次调用都会放在scopes前面(顶部)    

函数生成了就会有个属性 [[scopes]] 作用域"数组"(只能引擎使用)

9.4 函数闭包

官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包是什么?(面试题)
答:闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。

9.4.1 闭包的特点

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。

  2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

优点:可以访问局部变量。

缺点:局部变量一直占用内存,内存占用严重,还容易造成内存泄漏(内存被占用,剩余的内存变少,程序加载、处理速度变慢)。

9.4.2 闭包的写法

 第一种:写在原型对象的方法上

      function Person() {
              
          }
​
          Person.prototype.type="人类";
          Person.prototype.getType=function () {
              return this.type;
          }
          
          var person = new Person();
          console.log(   person.getType()   );//"人类"

第二种:内部函数语句访问外部函数的变量,将内部函数写在外部函数的return中

var Circle = function() { 
   //var this={}
   var obj = new Object();  //obj={}
   var a=100;
   obj.PI = 3.14159;  
     
   obj.area = function( r ) {  
       console.log(a)
       return this.PI * r * r;  
       //this访问了外部函数的变量obj
   }  
   return obj;  
    //return this;
}  
  
var c = new Circle();  //{PI:3.14159,area:function(){……}}
alert( c.area( 1.0 ) );  

第三种:通过表达式写在对象的方法上

var Circle = new Object();  
Circle.PI = 3.14159;  
Circle.Area = function( r ) {  
       return this.PI * r * r;  
}  
  
alert( Circle.Area( 1.0 ) );  
第四种:通过属性创建写在对象的方法上

var Circle={  
  PI:3.14159,  
  area:function(r){  
          return this.PI * r * r;  
        }  
};  
alert( Circle.area(1.0) );  

第四种:通过全局变量赋值(类似于第二种写法的原理)

var demo;
function test(){
    var aaa=100;
    function b(){
        console.log(aaa)
    }
    return b;
    //demo=b;
}
demo=test()
test();
demo();

9.4.3 闭包的用途

实现公有变量 (可以使用全局变量,函数设置计数器递增:

可以做缓存

可以实现封装,属性私有化

模块化开发,防止污染全局变量

实现类和继承

9.4.4 闭包的避免

9.4.5  闭包的面试

面试官问:闭包、回调、ES5和ES6的个人的一些东西,异步编程

谈谈对js中闭包的理解

1、直接回答问题的答案(总结:100%对)

2、这个技术的详细的东西(展开说)使用场景

3、特点:优缺点(缺点的解决方案或者替代方案)

4、项目中的真实情况

1.闭包的描述

      闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。

2.闭包的使用场景举例

       

3.闭包的看法(优点,缺点:解决)

       优点:上一步中 的技术难点 用闭包解决的方式

        1.函数内部的变量 想要被外部程序使用 但是语法决定了外部不能使用,可以利用闭包解决

        2.一些临时数据 比如for循环的i的值 希望延时业务中使用 可以使用闭包把临时数据保存到局部作用域中

        3.防止变量污染  可以用闭包把一些业务变量放在局部作用域中

        缺点:

        虽然闭包好用  可以解决很多问题  但是它玩不好的话就会有一些致命的问题:内存泄漏              

        内存管理机制:垃圾回收机制,引用计数  底层浏览器的代码实现的功能

        系统会定期查看我们的js执行情况,观察创建的对象有没有可能会被使用,如果没有可能 就释放内存,每一个对象都有"人"引用它  如果引用的"人"数为0就释放内存          

        内存泄漏:浏览器运行网页 就会执行js代码,引用数据会在内存中占用内存空间

        如果有一个对象创建了 而且占用了内存 缺没有什么业务使用(想用都用不了) 这种情况就是内存泄漏

   

内存泄漏的解决方案:

        1.尽量避开 不要使用闭包

        2.在可能存在泄漏的地方把标识符引用为null

闭包这个技术的好处是什么?  

通过业务点对闭包做区分

一:一个函数返回函数内部的工具函数,外部通过工具函数间接访问函数局部变量的过程

            函数内部的变量外部是无法访问的 但是可以通过返回一个闭包

            外部操作闭包来间接访问函数内部的变量,闭包可以决定这个变量的操作方式  

            (利用了函数的作用域和运行时作用域)  

二:利用函数自调用,每次调用内部的局部变量或者形参都是独立的 来保存一些临时数据

         利用了函数每次调用时生成的独立调用栈

         利用函数的形参保存临时变量的值

三、利用函数的独立作用域 来生成业务代码块  内部的变量相互不冲突污染全局变量

   //下面两种写法都是一样的
        fn()()  
        fn()
        ()
        //加分号才是两个语句
        fn();
        ()
        //所以闭包也要加分号
        (function () {})();

       

四、回调函数.利用函数复用的功能,制作复用工具,参数返回值

        利用函数复用的功能,制作复用工具,参数返回值

        闭包根据函数的使用场景不一样 业务不一样 可以有很多方面的回答 等等  

       

9.5 函数-特殊的对象

9.6 高阶函数

9.7 ​回调函数

英文是:callback           原名是:c钩子函数

主要思想:一函数1作为实参调用函数2,先运行函数2内的代码,再通过函数2的代码调用函数1

        function jquery(url,callback) {
            var res=url+"5s"
            callback(res)
        }
        //jquery 函数在开发中,就是一个工具函数,执行某个功能产生的数据,调用回调函数,执行业务代码
        //callback函数就是传入的业务函数
        jquery("http:wwww.baidu.con",function(data){
            console.log(data)
//function(Data)是再全局下运行的,即使它是在局部里调用的
        })   //结果为 http:wwww.baidu.con  5s

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值