函数、this、闭包、递归、深浅拷贝

JavaScript高级第03天笔记

1.函数的定义和调用

1.1函数的定义方式

  1. 方式1 函数声明方式 function 关键字 (命名函数)

    function fn(){}
    
  2. 方式2 函数表达式(匿名函数)

    var fn = function(){}
    
  3. 方式3 new Function()

    var f = new Function('a', 'b', 'console.log(a + b)');
    f(1, 2);
    
    var fn = new Function('参数1','参数2'..., '函数体')
    注意
    /*Function 里面参数都必须是字符串格式
    第三种方式执行效率低,也不方便书写,因此较少使用
    所有函数都是 Function 的实例(对象)  
    函数也属于对象
    */
    

1.2函数的调用

/* 1. 普通函数 */
function fn() {
	console.log('人生的巅峰');
}
 fn(); 
/* 2. 对象的方法 */
var o = {
  sayHi: function() {
  	console.log('人生的巅峰');
  }
}
o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
 btn.onclick = function() {};   // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000);  这个函数是定时器自动1秒钟调用一次
/* 6. 立即执行函数(自调用函数)*/
(function() {
	console.log('人生的巅峰');
})();

2.this

2.1函数内部的this指向

这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同

一般指向我们的调用者.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhxynOnX-1630458301371)(images/img1.png)]

2.2改变函数内部 this 指向

2.2.1 call方法

call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向

应用场景: 经常做继承.

var o = {
	name: 'andy'
}
 function fn(a, b) {
      console.log(this);
      console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3

以上代码运行结果查看开发者工具

2.2.2 apply方法

apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。

应用场景: 经常跟数组有关系

var o = {
	name: 'andy'
}
 function fn(a, b) {
      console.log(this);
      console.log(a+b)
};
fn()// 此时的this指向的是window 运行结果为3
fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3
2.2.3 bind方法

bind() 方法不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产生的新函数

如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind

应用场景:不调用函数,但是还想改变this指向

 var o = {
 name: 'andy'
 };

function fn(a, b) {
	console.log(this);
	console.log(a + b);
};
var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数
fn();//调用新函数  this指向的是对象o 参数使用逗号隔开
2.2.4 call、apply、bind三者的异同 ***
  • 共同点 : 都可以改变this指向

  • 不同点:

    • call 和 apply 会调用函数, 并且改变函数内部this指向.
    • call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
    • bind 不会调用函数, 可以改变函数内部this指向.
  • 应用场景

    1. call 经常做继承.
    2. apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
    3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向. ***

3.严格模式

3.1什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。

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

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

1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。

2.消除代码运行的一些不安全之处,保证代码运行的安全。

3.提高编译器效率,增加运行速度。

4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名

3.2开启严格模式

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

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

    • 有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
      script 脚本文件。

      (function (){
        //在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式
          "use strict";
             var num = 10;
          function fn() {}
      })();
      //或者 
      <script>
         "use strict"; //当前script标签开启了严格模式
      </script>
      <script>
        			//当前script标签未开启严格模式
      </script>
      
  • 情况二: 为函数开启严格模式

    • 要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。

      function fn(){
        "use strict";
        return "123";
      } 
      //当前fn函数开启了严格模式
      

3.3严格模式中的变化

严格模式对 Javascript 的语法和行为,都做了一些改变。

'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);  

更多严格模式要求参考

4.高阶函数

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yS3N4YXG-1630458301374)(images/img2.png)]

此时fn 就是一个高阶函数

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

同理函数也可以作为返回值传递回来

5.闭包 ***

5.1变量的作用域复习

变量根据作用域的不同分为两种:全局变量和局部变量。

  1. 函数内部可以使用全局变量。
  2. 函数外部不可以使用局部变量。
  3. 当函数执行完毕,本作用域内的局部变量会销毁。

5.2什么是闭包 ***

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

5.3闭包的作用

作用:延伸变量的作用范围。

可以扩展函数中的局部变量的作用域范围

可以减少定义 全局变量,避免全局污染 , 封装插件 封装组件的时候经常使用闭包

 function fn() {
   var num = 10;
   function fun() {
       console.log(num);
 	}
    return fun;
 }
var f = fn();
f();

5.4闭包的案例

输出索引号、定时器、函数防抖节流、私有变量封装

  1. 利用闭包的方式得到当前li 的索引号
        for(var i = 0; i < li.length; i++){
            // li[i].onclick = function(){
            //     console.log('index:' + i); //出来直接是5
            // };
            (function(){
                var index = i;   //立即执行函数,可以成为一个小的闭包区域,每个区域中狗有一个index,他们之间相互没有影响
                li[i].onclick = function(){
                    console.log('index:' + index); 
                }
            })()

        }
  1. 闭包应用-3秒钟之后,打印所有li元素的内容
        var li = document.querySelectorAll('li');
        for(var i = 0; i < li.length; i++){
            (function(){
                setTimeout(function(){
                    console.log(i);
                })
            }())
            setTimeout(function(){
                console.log(i);   //for 循环是同步运行的,setTimeout是异步操作,必须等同步运行的都运行完后才开始异步才做,所以开始运行时i已经打印到5了

            }, 3000)
        }
  1. 闭包应用-计算打车价格 选做
打车 起步价 10 (2公里),每多一公里增加 5元,
输入公里数,算出打车的价格,
如果有堵车的情况,总价格 就增加10var car = (function(){
            var start = 10;
            var total = 0;

            return{
                price: function(mileage){
                    if(mileage <= 2){
                        total = start;
                    }else{
                        total = start + (mileage - 2) * 5;
                    }
                    return total;
                },
                crowd: function(flag){
                    return flag? total + 10 : total;
                }
            }

        })();
        console.log(car.price(3));
        console.log(car.crowd(true));

        console.log(car.price(9));
        console.log(car.crowd(false));

        console.log(car.price(2));

5.6 防抖节流:***

5.6.1 防抖
对于短时间内连续触发的事件(滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
<div class="gotop"></div>

    <script>
        var gotop = document.querySelector('.gotop');

        window.onscroll = function(){
            //3. 获取离页面顶部的距离
            var top = document.documentElement.scrollTop;
            console.log(top);
            if(top >= 200){
                gotop.style.display = 'block';
            }
        }
        /*
        为什么需要使用 防抖?
        滚动事件,用户只要动一下关东条,滚动事件就会出发很多次严重影响性能

        防抖:
        让某个事件期限内,事件处理函数只执行一次
        可以用定时器
        */

        //使用防抖函数优化
        window.onscroll = debounce(go, 200);
        function go(){
            var top = document.documentElement.scrollTop;
            console.log(top);
            if(top >= 200){
                gotop.style.display = 'block';
            }
        }
        //封装一个防抖函数
        //参数1:回调函数    参数2:延时时间
        function debounce(fn, delay){
            var timer = null; //定时器初始值
            return function(){//闭包
                if(timer){  //定时器存在,先清除,再生成一个新的
                    clearTimeout(timer);
                    timer = setTimeout(fn, delay);
                }else{ //定时器不存在,生成定时器
                    timer = setTimeout(fn, delay);
                }
            }
        }
        /*
        思考:
        如果在200ms内一直触发这个时间,就会导致函数没办法执行
        希望:设计一个开关一样的东西,让某个函数执行一次后,在某个时间段内失效
        过了这段时间再开始激活状态(类似游戏放大招)
        */
    </script>

使用场景:滚动条滚动事件

基于上述场景,首先提出思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

5.6.2 节流

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?(此处暂且不论哪种方案更合适,既然产品爸爸说话了我们就先考虑怎么实现)

​ 其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

​ 效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

​ 实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

	<input type="text" placeholder="请输入搜索内容" />
    <script>
        var input = document.querySelector('input');

        //给input绑定按键弹起事件
        // input.onkeyup = function(){
        //     console.log(this.value);
        //     console.log('调用后台数据');
        // }
        /*搜索框的keyup时间导致 只要有字母输入就会触发,性能差, 可以使用节流来优化*/

        input.onkeyup = throttle(getdata, 500);
        function getdata(){
            console.log(input.value);
            console.log('调用后台数据');
        }

        
        //节流
        //参数: 1 回调函数   2 延时时间
        function throttle(fn, delay){
            var valid = true;  //记录状态 激活 or 失效
            return function(){
                if(!valid){ //resting
                    return false;
                }
                valid = false;
                setTimeout(function(){
                    fn(); //调用函数
                    valid = true; //激活状态
                }, delay) 
            }
        }
    </script>

防抖和节流的区别
1、防抖(debounce):高频触发事件,ns内只执行一次,如果ns内事件再次被触发,则重新计算时间
在一定时间内持续触发该事件,有可能事件绑定的函数一次都触发不到
如:滚动条事件

​ 举例:就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。

​ 节流(thorttle):高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率

​ 举例:预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

​ 2、区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

5.7 闭包的缺点

将 函数的局部变量转为 全局变量,导致 全局变量永驻内容,会产生内存泄漏

6.递归 ***

6.1什么是递归

**递归:**如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数

**注意:**递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。

6.2利用递归求1~n的阶乘

//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
 function fn(n) {
     if (n == 1) { //结束条件
       return 1;
     }
     return n * fn(n - 1);
 }
 console.log(fn(3));

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K1ENk6gz-1630458301378)(images/img6.png)]

6.3利用递归求斐波那契数列

var n = prompt('请输入n:')
 
function Fibonacci(n){
    if(n == 1 || n == 2){
        return 1;
    }
    else{
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}
 
document.write(Fibonacci(n))

6.4利用递归遍历数据

// 我们想要做输入id号,就可以返回的数据对象
 var data = [{
   id: 1,
   name: '家电',
   goods: [
   {id: 11, gname: '冰箱', goods: [{id: 111, gname: '海尔'}, {id: 112, gname: '美的'}]}, 
    {id: 12, gname: '洗衣机'}]}, 
    {id: 2, name: '服饰'}];
//1.利用 forEach 去遍历里面的每一个对象
 function getID(json, id) {
   var o = {};
   json.forEach(function(item) {
     // console.log(item); // 2个数组元素
     if (item.id == id) {
       // console.log(item);
       o = item;
       return o;
       // 2. 我们想要得里层的数据 11 12 可以利用递归函数
       // 里面应该有goods这个数组并且数组的长度不为 0 
     } else if (item.goods && item.goods.length > 0) {
       o = getID(item.goods, id);
     }
   });
   return o;
}

7. 深浅拷贝 ***

会引出

简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

1、区别: 浅拷贝/深度拷贝
判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用
知识点:对象数据存放的是对象在栈内存的引用,直接赋值的是对象的引用

2、常用的拷贝技术
2.1 数组的一层深浅拷贝

可以使用 循环赋值、slice、concate完成

2.2 对象的一层深拷贝

可以使用 循环赋值、assign完成

2.3 多层深拷贝

可以使用 序列化和反序列化

可以使用 jquery 的$.extend 进行深拷贝

可以使用 自己封装的深拷贝函数

1. 深拷贝

1.1、js数组的slice方法

var arr1 = [“前端”,“安卓”,“苹果”];
var arr2 = arr1.slice(0);    //   var arr2 = arr1;   //浅拷贝, 直接赋值  log的两个结果一样,都赋了新值
arr2[0] = “后端”;
console.log(“原始值:” + arr1 );//前端,安卓,苹果
console.log(“新值:” + arr2);//后端,安卓,苹果

理解:通过JS的slice方法,改变拷贝出来的数组的某项值后,对原来数组没有任何影响。
缺点:适用于对不包含引用对象的一维数组的深拷贝

1.2、js数组的concat方法

var arr1 = [“前端”,“安卓”,“苹果”];
var arr2 = arr1.concat();
arr2[0] = “后端”;
console.log(“原始值:” + arr1 );//前端,安卓,苹果
console.log(“新值:” + arr2);//后端,安卓,苹果

理解:concat方法,原数组和新数组修改某值后,不会改变。
缺点:适用于对不包含引用对象的一维数组的深拷贝

1.3 js遍历数组

var arr1 = ["前端","安卓","苹果"];;//原来数组
var arr2 = [];//新数组
function deepCopy(arry1, arry2){
    var length = arry1.length;
    for(var i = 0;i<length;i++){
	arry2[i] = arry1[i];
    }
}
deepCopy(arr1, arr2);
arr2[0] = "后端";
console.log(arr1);
console.log(arr2);

理解:通过对数组个元素进行遍历,然后赋值到新数组,改变原数组或者新数组,不会影响新数组或原数组。

缺点:如果包含二维数组,要进行单独处理,方法重写。

重写理解:通过循环遍历数组个元素,并且判断各元素下是否包含下级,来进行遍历。 改变拷贝出来的数组的某项值后,对原来数组没有任何影响

1)arr.concat(): 数组深拷贝
2). arr.slice(): 数组深拷贝
3).Object.assign()对象深拷贝
4). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据
5). 浅拷贝包含函数数据的对象/数组
6). 深拷贝包含函数数据的对象/数组
对于多层对象和数组:可以使用 递归调用、JSON.parse(JSON.stringify(arr/obj))、jquery的extend方法来实现深拷贝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值