JS——函数(学习笔记)

1:初识函数

函数是程序的主要“构建模块”。函数使该段代码可以被调用很多次,而不需要写重复的代码。

JavaScript 使用关键字 function 定义函数。函数可以通过声明定义,也可以是一个表达式。

 2:函数声明

    // 函数的使用 分为两步:声明函数和调用函数
        //声明函数
        // function 函数名() {
        //     //函数体
        // }
        // 调用函数

        // 1:function 声明函数的关键字 全部小写
        // 2: 函数是做某件事函数名一般是动词
        // 3:函数不调用自己不执行

        function getSum() {
            var sum = 0;
            for (var i = 0; i <= 100; i++) {
                sum += i;
            }
            console.log(sum);
        }
        // 调用函数
        getSum();

        //函数的作用:功能的封装,直接调用,代码复用率提高 

3:函数的参数

       //   利用函数的参数实现函数重复不同的代码
        // function 函数名(形参1;形参2...){// 在声明函数的小括号里面的是形参  
        //     // 函数体
        // }
        // 函数调用
        // 函数名(实参1, 实参2...)   //在函数调用的小括号里面是实参


        // 形参:形式上的参数 函数定义时 传递的参数 当时并不知道什么
        // 实参: 实际上的参数  函数调用的时候传递的参数  实参是传递给形参的
        // 参数的作用:在函数内部某些值不能固定 我们可以通过参数在调用函数的传递不同值进去

        // 3:形参和实参的执行过程
        function cook(a) { //形参是接收实参的 a=123459  形参类似于一个变量
            console.log(a);
        }
        console.log(123459);

        function getSum(num1, num2) {
            console.log(num1 + num2);
        }
        getSum(1, 9)
        getSum(2, 4)

 案例:函数封装冒泡排序

     function sort(arr) {
            for (var i = 0; i < arr.length - 1; i++) {
                for (var j = 0; j < arr.length - i - 1; j++) {
                    if (arr[j] > arr[j + 1]) {
                        var temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;

                    }
                }
            }
            return arr;

        }
        console.log(sort([1, 5, 4, 9, 13, 56, 3]));
        var arr1 = sort([1, 5, 4, 9, 13, 56, 3]);
        console.log(arr1);

4:函数形参实参个数匹配

    // 函数形参实参个数匹配
        function getSum(num1, num2) {
            console.log(num1 + num2);
        }
        // 1. 如果实参的个数和形参的个数一致,则正常输出结果
        getSum(1, 2); //3
        //2:如果实参的个数多于形参的个数  //会取到形参的个数
        getSum(1, 2, 3) //3 
        //3:如果实参的个数小于形参的个数    多于形参定义为underfined  结果为NaN
        //形参可以看做是不用声明的变量  num2是一个变量没有接受值 结果就是underfined
        getSum(1) //NaN

5:函数的返回值

     函数的返回值格式

      function 函数名() {

            // return 需要返回的结果

         }

       函数名();

        1:我们函数只是实现某种功能,最终的结果需要返回给函数的调用者函数()通过return实现

        2:只要函数遇到return 就把后面的结果 返回给函数的调用者

 function getResult() {
            return 666;
        }
        getResult(); //getResult
        console.log(getResult());

        // 函数返回值注意事项
        // 1.return 终止函数
        function getSum(num1, num2) {
            return num1 + num2;
            alert('熊二')
        }
        console.log(getSum(1, 3));
        // 2:return  只能返回一个值
        function fn(num1, num2) {
            return num1, num2;
        }
        console.log(fn(1, 2));
        // 4: 我们的函数如果有return 则返回的是return 后面的值   如果函数没有 return  则返回underfined

6:arguments的使用

   当我们不确定 有多少个参数传递的时候,可以用arguments来获取,在js中 arguments实际上它是当前函数的一个内置对象,所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。

arguments是一个类数组对象,包含着传入函数中的所有参数。arguments主要用途是保存函数参数,但是这个对象还有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

     function fn() {
            console.log(arguments); //里面储存了所有传递过来的实参
            console.log(arguments.length);
            console.log(arguments[2]);
        }
        fn(1, 2, 3)
        //伪数组,并不是真正意义上的数组
        //1:具有数组length的属性
        // 2:按照索引的方式储存
        // 他没有真正数组的一些方法 pop()等

 案例:

function add(a,b){
  console.log(arguments[0],arguments[1],arguments[2],arguments[3]);
  console.log(a+b);
}
add(10);
//10 undefined undefined undefined
//NaN
add(10,20);
//10 20 undefined undefined
//30
add(10,20,30);
//10 20 30 undefined
//30
add(10,20,30,40);
//10 20 30 40
//30

7:作用域

        1: javaScript 作用域:就是代码名字(变量) 在某个范围内起作用和效果;目的是为了提高              程序的可靠性      更重要的是减少命名冲突

        2: js的作用域 (es6 )之前 :全局作用域  局部作用域

        3:全局作用域 :整个script标签  或者是一个单独的js文件

        4:局部作用域 在函数内部就是局部作用域 这个代码名字只在函数内部起效果和作用

 1:局部作用域

在函数中声明的变量只在该函数内部可见。 

在 JavaScript函数中声明的变量,会成为函数的局部变量。

函数内部声明的变量,在函数外部不能访问。

  var num = 10;

        function fn() {
            var num = 20;
            console.log(num); //20
        }
        fn()
        console.log(num); //10

2:全局作用域

函数之外声明的变量,会成为全局变量。

函数外部声明的变量,在函数内部可以访问。

当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。

    //全局作用域:global/window/本文件内
        var v1 = 10;
        v2 = 20; // 所有末定义直接赋值的变量自动声明为拥有全局作用域
        function foo() {
            //函数作用域、局部作用域
            var a = 3;
            console.log(v1, v2);
            console.log(this);
        }
        foo()
        console.log(a); // a is not defined

 3:变量作用域

        // 变量的作用域: 根据作用域的不同我们变量分为全局变量和局部变量
        // 1:全局变量:在全局作用域下的变量
        var num = 10;
        console.log(num);

        function fn() {
            console.log(num);
        }
        fn()

        // 2:局部变量  在局部作用域下 的变量  后者在函数内部的变量就是 局部变量
        function fun() {
            var num1 = 20 //num1就是局部变量  只能在函数内部使用
        }
        fun()
        console.log(num1);

4:作用域链

当代码要访问一个变量时 —— 首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。

 // 作用域  :内部函数访问外部函数 采取的就是链式查找的方式来决定取哪个值
        var num = 10;

        function fn() { //外部函数
            var num = 30;

            function fun() { //内部函数
                console.log(num);

            }

        }

      // 如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。
        var a = 100

        function F1() {
            var b = 200

            function F2() {
                var c = 300
                console.log(a) // 自由变量,顺作用域链向父作用域找 //100
                console.log(b) // 自由变量,顺作用域链向父作用域找 //200
                console.log(c) // 本作用域的变量  //300
            }
            F2()
        }
        F1()

8:this的使用

1:对象方法,"this"

在对象方法中, this 指向调用它所在方法的对象。

var user = {
  name: "小明",
  age: 30,

  sayHi() {
    // "this" 指的是“当前的对象”
    alert(this.name);
  }

};

user.sayHi(); // 小明

2:单独使用this

/ 单独使用 this
        //2: - 在函数中,this 表示全局对象。
        // - 如果单独使用,this 表示全局对象。

        //         单独使用 this,则它指向全局对象。

        // 在浏览器中,window 就是该全局对象为 [**object Window**]:

        // 在node中,指向的是一个{}
        var x = this;
        console.log(x); //window

3:函数中使用 this(默认)

        //     在函数中,函数的所属者默认绑定到 this 上。

        // 在浏览器中,window 就是该全局对象为 [**object Window**]:

        // 在node中,指向的就是global对象

     
        function myFunction() {
            return this;
        }
        console.log(myFunction()); // window

4:事件中的this

  <button onclick="this.style.display='none'"> 点我后我就消失了 </button>

9:预解析

 //js引擎运行js 分为两步 :预解析  代码执行

        //1.预解析 js引擎会把js 里面所有的 var 还有function 提升到当前作用域的最前面

        //代码执行。按照代码书写顺序从上往下执行。

        // 2 预解析分为 变量预解析(变量提升)  和函数预解析(函数提升)

        // (1)变量提升  就是把所有的变量声明提升到当前的作用域最前面   不提升赋值操作

        //函数提升  就是把所有的函数声明提升到当前的作用域最前面   不调用函数

   // 1:问
        console.log(num);
        // 2问
        console.log(num);
        var num = 10;
        //相当于执行以下代码
        // var num;
        // console.log(num);
        // num=10;


        // 3问
        fn();

        function fn() {
            console.log(11);
        }
        //4问
        fun();
        var fun = function () {
            console.log(12)
        }
        //相当于执行以下代码
        // var fun;
        // fun();
        // fun = function () {
        //     console.log(22);
        // }

10:自由变量

        // 首先认识一下什么叫做 ** 自由变量 ** 。如下代码中, `console.log(a)`
        // 要得到a变量, 但是在当前的作用域中没有定义a( 可对比一下b)。 
        // 当前作用域没有定义的变量, 这成为 自由变量。
        //  自由变量的值如何得到—— 要到创建这个函数的那个父级作用域寻找, 
        //  如果没有就一直向上级祖先元素寻找( 这就是所谓的"静态作用域")
        var a = 100

        function fn() {
            var b = 200
            console.log(a) // 这里的a在这里就是一个自由变量  // 100
            console.log(b)
        }
        fn()

11:IIFE

// IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。

        // 作用

        // - 1:页面加载完成后只执行一次的设置函数。

        // - 2:将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量

//IIFE的函数调用
(function foo(){
  var a = 10;
  console.log(a);
})();

1: IIFE的基本使用

// 就像其它任何函数一样,一个立即执行函数也能返回值并且可以赋值给其它变量。
var sum = (function (a,b) {
  return a + b;
}(1,2))
console.log(sum);

 2:为什么需要IIFE

IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。那么如何实现作用域的隔离呢?在JS中,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。

 3:IIFE经典面试题

for (var i = 0; i < 6; i++) {
  function output() {
    console.log(i); // 为什么输出的是6,而不是0,1,2,3,4,5
    // 因为输出的 i 是全局作用域的,当循环结束后 i 的值是 6,所以输出的 i 就是6。
  }
}
output()
for (var i = 0; i < 6; i++) {
  (function (j) {
    console.log(j); //0,1,2,3,4,5
  })(i)
  // 因为 JS 中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数 i 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以每个输出访问的都是不同作用域的 i 的值 。
}

4:IIFE常见形式

1.对返回结果不进行处理

(function(形参){
	函数体内容
})(实参);

2.对返回结果不进行处理

(function(形参){
	函数体内容
}(实参));

3.返回的是一个布尔值,然后进行取反

!function(形参){
	函数体内容
}(实参)

4.对于数字返回的是原来的结果,非数字返回NaN

+function(形参){
	函数体内容
}(实参)

5.对于数字返回的是正负符号相反,非数字返回NaN

-function(形参){
	函数体内容
}(实参)

6.对于数字返回的是正负符号相反再减1,非数字返回-1

~function(形参){
	函数体内容
}(实参)

7.返回的结果是undefined

void function(形参){
	函数体内容
}(实参)

12:函数调用

调用函数的方式不仅限于()执行,还有其他几种方式

  • 函数名(实参列表);

  • 函数名.call(执行环境对象,实参列表);

  • 函数名.apply(执行环境对象,实参列表数组);

  • 函数名.bind(执行环境对象)(实参列表);

 1:函数调用形式

// 直接声明一个函数后调用
function foo() {
    console.log('熊大');
    
}
foo();
// 使用函数的Lambda表达式定义函数,然后调用
var fun =function(){
    console.log('熊二');
}
fun();

2:对象的方法

通过建立对象后,来完成对函数的调用

var obj = {
  name: '李易峰',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b(); //undefined
obj.sayName(); // 李易峰

那我们有什么方法可以让b(),this指向到obj中呢?

有时候我们不得不将这个对象保存到另外的一个变量中,那么就可以通过以下方法。

3:call(执行环境对象,实参列表);

调用call方法,第一个参数就是要把b添加到哪个环境中,简单来说,this就会指向那个对象。

var obj = {
  name: '李易峰',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b.call(obj); // 李易峰

在使用call调用的时候,还可以传递多个参数

var obj = {
  name: '李易峰',
  sayName: function (a,b) {
    console.log(this.name);
    console.log(a,b); // 1,2
  }
}
var b = obj.sayName;
b.call(obj,1,2); // 李易峰

4:apply(执行环境对象,实参列表数组);

apply方法和call方法有些相似,它也可以改变this的指向

var obj = {
  name: '李易峰',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b.apply(obj); // 李易峰

同样apply也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

var obj = {
  name: '李易峰',
  sayName: function (a,b) {
    console.log(this.name);
    console.log(a,b); // 100,200
  }
}
var b = obj.sayName;
b.apply(obj,[100,200]); // 李易峰

 注意:如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象

var obj = {
  name: '李易峰',
  sayName: function () {
    console.log(this); // global
  }
}
var b = obj.sayName;
b.apply(null); // 李易峰

5:bind(执行环境对象)(实参列表);

var obj = {
  name: '李易峰',
  sayName: function () {
    console.log(this.name);
  }
}
var b = obj.sayName;
b.bind(obj); // 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj);
console.log(c); // 发现c是一个[Function: bound sayName]函数
// 执行c
c(); // 李易峰

​​​​​​​同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

var obj = {
  name: '李易峰',
  sayName: function (a,b,c) {
    console.log(this.name);
    console.log(a,b,c); // 1,2,3
  }
}
var b = obj.sayName;
b.bind(obj); // 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj, 1, 2);
console.log(c); // 发现c是一个[Function: bound sayName]函数
// 执行c
c(3); // 李易峰

总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。

 13:闭包

  • 什么是闭包?

  • 闭包 是指一个函数可以记住其外部变量并可以访问这些变量。在某些编程语言中,这是不可能的,或者应该以一种特殊的方式编写函数来实现。

简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。

MDN 上面这么说闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

闭包的生成有三个必要条件

  • 函数嵌套函数

  • 内部函数引用了外部函数中的数据(属性、函数)

  • 参数和变量不会被回收

这样就形成了一个不会销毁的函数空间

  • 产生一个闭包

创建闭包最常见方式,就是在一个函数内部创建另一个函数。

function func() {
  var a = 1, b = 2;

  function closure() {
    return a + b;
  }
  return closure;
}
console.log(func()()); // 3

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
//如果我们调用 counter() 多次,count 变量将在同一位置增加到 2,3 等。

 在每次 makeCounter() 调用的开始,都会创建一个新的词法环境对象,以存储该 makeCounter 运行时的变量。

不同的是,在执行 makeCounter() 的过程中创建了一个仅占一行的嵌套函数:return count++。我们尚未运行它,仅创建了它。

因此,counter.[[Environment]] 有对 {count: 0} 词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。[[Environment]] 引用在函数创建时被设置并永久保存。

稍后,当调用 counter() 时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于 counter.[[Environment]]上述例子参考资料

function f1() {
  var n = 999;
  nAdd = function () { n += 1 }
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

 使用闭包注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露,这是IE的BUG。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。多个子函数的scope都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。

开发者通常应该都知道“闭包”这个通用的编程术语。

闭包是指一个函数可以记住其外部变量并可以访问这些变量。在某些编程语言中,这是不可能的,或者应该以一种特殊的方式编写函数来实现。但如上所述,在 JavaScript 中,所有函数都是天生闭包的

也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。

在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节。参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值