JS 函数进阶

函数的定义方式

函数声明(自定义函数)命名函数

function foo () {

}

函数表达式 匿名函数

var foo = function () {

}

new Function

var fn = new Function('var name = "zs";console.log(name)');

Function里面的参数都必须是字符串的形式

这种new的方法效率低,不方便,使用较少。

所有函数都是 Function 的实例

函数也是对象

<script>
        //1.
        function fun() {}

        //2.
        var fun = function() {}

        //3.
        var f = new Function('a','b','console.log(a+b)');

        f(1,2);
        console.dir(f)
    </script>

在这里插入图片描述

函数声明与匿名函数的区别

1.函数声明必须有名字

2.函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用

3.匿名函数类似于变量赋值

4.匿名函数可以没有名字

5.匿名函数没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

函数的调用方式

<script>
        //1.普通函数
        function fn() {
            console.log('牛马');
        }
        fn();
        fn.call();

        //对象的方法
        var o = {
            saihi:function() {
                console.log('牛马');
            }
        }
        o.saihi();


        //构造函数
        function Star() {}
        var star = new Star();


        //绑定事件函数
        btn.click = function() {}  //绑定的btn自己调用


        //定时器函数
        setInterval(function() {},1000); //定时器每隔一秒调用一次


        //立即执行函数
        (function() {
            console.log('牛马'); //自动调用
        })();

    </script>

函数内this指向的不同场景

在这里插入图片描述

<script>
        //1.普通函数
        function fn() {
            console.log('函数的this指向' + this);
        }
        window.fn();
        fn.call();
        //this指向window


        //对象的方法
        var o = {
            saihi:function() {
                console.log('函数的this指向' + this);
            }
        }
        o.saihi();
        //this指向 o 这个对象


        //构造函数
        function Star() {}
        var ldh = new Star();

        Star.prototype.sing = function() {

        }
         //构造函数的this指向ldh这个实例对象   原型对象里的this也指向ldh这个对象


        //绑定事件函数
        btn.click = function() {}  //绑定的btn自己调用,this指向btn


        //定时器函数
        wicdow.setInterval(function() {},1000); //定时器每隔一秒调用一次 this指向window


        //立即执行函数
        (function() {
            console.log('函数的this指向' + this); //自动调用 this指向window
        })();


    </script>

call、apply、bind

JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有

bind()
call()
apply() 

call

call() 方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)

注:该方法的作用和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。

语法

fun.call(thisArg, arg1, arg2, ...)
参数

thisArg
在 fun 函数运行时指定的 this 值
如果指定了 null 或者 undefined 则内部 this 指向 window

arg1, arg2, …
指定的参数列表

<script>
        //call 第一个作用可以调用函数
        function fun() {
            console.log(this)
        }
        var o = {
            name:'andy'
        }

        //call 第二个作用可以改变this指向
        fun.call(o);


        //call 主要作用用于继承
        function Father(name,age) {
            this.name = name;
            this.age = age
        }

        function Son(name,age) {
            Father.call(this,name,age);
        }

        var son = new Son('lihua',22);
        console.log(son);
    </script>

apply

apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。

语法:

fun.apply(thisArg, [argsArray])

例如:

fun.apply(this, ['eat', 'bananas'])
<script>
        //apply 第一个作用可以调用函数
        function fun(arr) {
            console.log(this)
            console.log(arr); //blue
        }
        var o = {
            name:'andy'
        }

        //apply 第二个作用可以改变this指向
        fun.apply(o,['red']);


       //apply其他参数必须是一个数组(伪数组)

       //apply应用:可以使用数学内置函数求最大值
       var arr = [1,22,5,11,8];
       console.log(Math.max.apply(Math,arr)); //22
    </script>

bind

在这里插入图片描述
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数

参数

thisArg
当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

arg1, arg2, …
当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值:返回由指定的this值和初始化参数改造的原函数拷贝。

 <script>
        //bind 不可以调用函数
        function fun(a,b) {
            console.log(this)
            console.log(a+b);
            
        }
        var o = {
            name:'andy'
        }

        //bind 可以改变this指向
        var f = fun.bind(o,1,2);


        //返回的是原函数改变this后产生的新函数
        f();

        //如果我们不想立即调用函数  但是想改变它的内部this指向  此时需要bind方法
        var btn = document.querySelector('button');

        btn.onclick = function () {
            this.disabled = true;

            setTimeout(function() {
                this.disabled = false
            }.bind(this),3000)
        }

        //正常情况下 定时器的this指向window  通过bind改变this指向btn
    </script>

小结

  • 共同点 : 都可以改变this指向

  • 不同点:

    • call 和 apply 会调用函数, 并且改变函数内部this指向.
    • call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
    • bind 不会调用函数, 可以改变函数内部this指向.
  • 应用场景
    1.call 经常做继承.
    2. apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
    3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

严格模式

什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。

ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

严格模式对正常的 JavaScript 语义做了一些更改:

1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
	
2.消除代码运行的一些不安全之处,保证代码运行的安全。
	
3.提高编译器效率,增加运行速度。
	
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法
  为未来新版本的 Javascript 做好铺垫。
  比如一些保留字如:class,enum,export, extends, import, super 不能做变量名

开启严格模式

严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为**脚本开启严格模式和为函数开启严格模式**两种情况。

情况一 :为脚本开启严格模式

  • 有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
    script 脚本文件。
  <script>
        //为某个函数添加严格模式
        'use strict'
        //下面的js代码 会按照严格模式执行
    </script>

    <script>
        (function () {
            'use strict';
        })();
    </script>

情况二: 为函数开启严格模式

  • 要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。
<script>
        //为某个函数添加严格模式
        function fn() {
            'use strict'
            //下面代码会按照严格模式执行
        }

        function fun() {
            //这里的还是按照普通模式执行
        }
    </script>

严格模式的变化

变量:

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量.

严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用

严禁删除已经声明变量。例如,delete x; 语法是错误的

this 指向问题:

严格模式下全局作用域中函数中的 this 是 undefined

严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果赋值,会报错

new 实例化的构造函数指向创建的对象实例。

定时器 this 还是指向 window

事件、对象还是指向调用者

函数变化

函数不能有重名的参数

函数必须声明在最前面.新版本的JavaScript会引入“块级作用域”
在这里插入图片描述

( ES6 中已引入)为了与新版本接轨,不允许在非函数的代码块内声明函数

代码演示:

'use strict'
num = 10 
console.log(num)//严格模式后使用未声明的变量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//严格模式不允许删除变量
--------------------------------------------------------------------------------
function fn() {
 console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined
}
fn();  
---------------------------------------------------------------------------------
function Star() {
	 this.sex = '男';
}
// Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
var ldh = new Star();
console.log(ldh.sex);
----------------------------------------------------------------------------------
setTimeout(function() {
  console.log(this); //严格模式下,定时器 this 还是指向 window
}, 2000);  

function fn(a,a) {
	console.log(a+a);
	}
	fn(1,2);//不被允许  因为a重名

更多严格模式参考

函数的其他成员

arguments:实参集合
caller:函数的调用者
length:形参的个数
name:函数的名称

function fn(x, y, z) {
  console.log(fn.length) // => 形参的个数
  console.log(arguments) // 伪数组实参参数集合
  console.log(arguments.callee === fn) // 函数本身
  console.log(fn.caller) // 函数的调用者
  console.log(fn.name) // => 函数的名字
}

高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。

函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。

同理函数也可以作为返回值传递回来
在这里插入图片描述
代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../jquery.min.js"></script>
    <style>
        div{
            width: 200px;
            height: 200px;
            background-color: black;
            position: absolute;
        }
    </style>
</head>
<body>
    <div></div>
    <script>
        function fn(a,b,callback) {
            console.log(a+b);
            callback && callback();
        }
        fn(1,2,function() {
            console.log('我是最后调用的');
        })

        $("div").animate({
            left:800
        },function() {
            $('div').css("height","1000")
        })
    </script>
</body>
</html>

函数闭包

什么是闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。

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

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
在这里插入图片描述

在 chrome 中调试闭包

  1. 打开浏览器,按 F12 键启动 chrome 调试工具。
  2. 设置断点。
  3. 找到 Scope 选项(Scope 作用域的意思)。
  4. 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
  5. 当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。

为什么会有闭包

由于作用域的原因,我们无法在函数外访问函数里面定义的变量,但有时候我们又会有这样的需求,这个时候我们就需要使用闭包了。

闭包的用途

可以在函数外部读取函数内部成员

让函数内成员始终存活在内存中

延伸变量的作用范围。

 <script>
        //闭包的主要作用:延伸了变量的作用范围
        //正常情况下  f无法访问fn内部变量
        //通过闭包 f可以访问fn内部的局部变量num
        function fn() {
            var num = 10;
            return function() {
                console.log(num);
            }
        }
        var f = fn();
        // 类似于
        // var f = function() {
        //     console.log(num)
        // }
        
        f();
    </script>

闭包案例

1.点击li标签显示索引

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="nav">
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </div>
    <script>
        //1.利用动态添加属性的方式
        var lis = document.querySelector('.nav').querySelectorAll('li')
        for(var i = 0;i < lis.length; i++) {
            lis[i].index = i;
            lis[i].onclick = function() {
                console.log(this.index);
            }
        }

        //2.利用闭包的方式
        for(var i = 0;i < lis.length; i++) {
           //利用for循环创建4个立即执行函数
           //立即执行函数也成为小闭包 因为立即执行函数里面的任何一个函数都可以使用它的li变量
           (function(i) {
               lis[i].onclick = function() {
                   console.log(i);
               }

           })(i)
        }
        //当循环开始  
        //i为0 会把它传给立即执行函数里的i   
        //然后立即执行函数里的i把他传到立即执行函数function的i里  
        //lis[i]  拿到i
        //然后传递到console.log(i)里
    </script>
</body>
</html>

2.定时器显示li标签内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="nav">
        <ul>
            <li>柚子</li>
            <li>柠檬</li>
            <li>西瓜</li>
            <li>草莓</li>
        </ul>
    </div>
    <script>
        var lis = document.querySelector('.nav').querySelectorAll('li')
        //利用闭包的方式
        for(var i = 0;i < lis.length; i++) {
           //利用for循环创建4个立即执行函数
           //立即执行函数也成为小闭包 因为立即执行函数里面的任何一个函数都可以使用它的li变量
           (function(i) {
               setTimeout(function() {
                   console.log(lis[i].innerHTML);
               },2000)
           })(i)
           
        }
    </script>
</body>
</html>

效果:
在这里插入图片描述
3.打车案例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        var car = (function() {
            var start = 13;
            var total = 0;

            return {
                price:function(n) {
                    if(n<3) {
                        total = 13;
                    }else {
                        total = start + (n-3) * 5;
                    }
                    return total;
                },
                //拥堵之后
                yd:function(flag) {
                    return flag ? total + 10 : total;
                }
            }
        })();

        console.log(car.price(5)); //23
        console.log(car.yd(true)); //33

        console.log(car.price(1)); //13
        console.log(car.yd(false)); //13
    </script>
</body>
</html>

闭包的好处与坏处

好处

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

坏处

①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

闭包理解参考闭包

思考

<script>
        var name = "The Window";
        var object = {
            name: "My Object",
            getNameFunc: function () {
                return function () {
                    return this.name;
                };
            }
        };
        console.log(object.getNameFunc()())

        //object.getNameFunc()()
        //类似于 
        //var f = function() {return this.name}  f();

        //即 function() {this} ()  这里相当于立即执行函数  this指向window  所以输出结果为The window

        
        
    </script>

这里没有闭包 因为函数内没有任何局部变量,没有调用。
2.

<script>
        var name = "The Window";
        var object = {
            name: "",
            getNameFunc: function () {
                var that = this;  
                //这里的this指向object  并把它存到that中
                
                return function () {
                    return that.name;  
                    //这里that指向object  返回object的name  My Object
                };
            }
        };
        console.log(object.getNameFunc()()) //输出结果为My Object
        //类似于 var f = object.getNameFunc()  var f = function () {return that.name;}  f();
    </script>

这里存在闭包 因为that为局部变量 且

return function () {
                    return that.name;  
                    //这里that指向object  返回object的name  My Object
                };

调用了that。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值