03.函数进阶

208 阅读6分钟

一、函数的定义

  • 自定义函数(命名函数)

    function fn() {};
    
  • 函数表达式(匿名函数)

    var fun = function() {};
    
  • new Function ( '参数1' , '参数2' , '参数3' ) ;

    var f = new Function('a','b','console.log(a + b)');	// 语句直接用字符串的形式输出
    f(1,2);  // 传实参
    
    • 所有的函数都是Function的实例(对象)

二、函数的调用

  1. 普通函数 this指向 window

    function fn() {
        console.log('hi');
    }
    window.fn();
    fn.call();
    
  2. 对象的方法 this指向对象 o

    var o = {
        sayHi: function() {
            console.log('hi');
        }
    }
    o.sayHi();
    
  3. 构造函数 this指向实例对象 cy ,原型对象( Star.prototype.sing)里面的 this也指向 cy这个实例对象

    functoin Star() {
        console.log('hi');
    }
    var cy = new Star();
    
  4. 绑定事件函数 this指向调用者 btn

    btn.onclick = function() {
        console.log('hi');
    }
    
  5. 定时器函数 this指向 window

    window.setInterval(function() {
         console.log('hi');
    },1000)
    
  6. 立即执行函数 -->自动调用 this指向 window

    (function() {
         console.log('hi');
    })()
    

1. 改变函数内部指向

1). call

fun.call (thisArg, arg1, arg2, ...)
  • 调用函数
  • 改变函数内部 this指向
        var o = {
            name: 'Andy'
        }
        function fn(a,b) {
            console.log(this);	// {name:"Andy"}
            console.log(a+b);	// 3
        }
        fn.call(o, 1, 2);
  • 继承

		function Father(uname, age){
            this.uname = uname;
            this.age = age;
        }
        function Son(uname, age, score){
            // 调用父构造函数 把父构造函数里面的this改成子构造函数的this
            Father.call(this, uname, age);
            this.score = score;
        }
        var six = new Son('春杨', 21, 100);
        console.log(six);
        //Son {uname: "春杨", age: 21}
        //age: 21
        //uname: "春杨"
        //__proto__: Object

2). apply

fun.apply(this.Arg, [argArry])
  • 必须以数组的形式传递

  • 调用函数

  • 改变函数内部 this指向

  • 借助数学内置对象

        var o = {
            name: 'Andy'
        }
        function fn(arr) {
            console.log(this);  // {name: "Andy"}
            console.log(arr);   // pink
        }
        fn.apply(o,['pink']);

        var arr = [6,7,3,8];
        var max = Math.max.apply(Math, arr);
        console.log(max);	// 8

3). bind

fun.bind (thisArg, arg1, arg2, ...)
  • 改变函数内部 this指向

只绑定 不调用

        var o = {
            name: 'Andy'
        }
        function fn(arr) {
            console.log(this);
        }
        var f = fn.bind(o); // 只绑定 不调用
        f();
    <button>点击</button>
    <button>点击</button>
    <button>点击</button>
    <script>
        // 有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
/*      var btn = document.querySelector('button');
        btn.onclick = function() {
            this.disabled = true; 	// 禁用按钮
            setTimeout(function() {
            this.disabled = false;
        }.bind(this), 1000)
        } */

        var btn = document.querySelectorAll('button');
        for(var i = 0; i < btn.length; i++) {
            btn[i].onclick = function() {
                this.disabled = true;
                setTimeout(function() {
                this.disabled = false;
                }.bind(this), 1000)
            }
        }
        
</script>    

三、严格模式 use strict

1. 为脚本开启严格模式

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

以将整个脚本文件 放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。

2. 为函数开启严格模式

function fn(){
    "use strict";   
    return "这是严格模式。"; 
} 

给某一个函数开启了严格模式。

3. 严格模式中的变化

1). 变量

  • 变量都必须先用 var 命令声明。

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

2). this 指向问题

以前严格模式
全局作用域函数中的 this 指向对象windowundefined
构造函数不加new也可以 调用,当普通函数,this指向全局对象不加new调用, this 指向的是undefined,赋值会报错
new 实例化的构造函数指向创建的对象实例
定时器 this 还是指向 window
事件、对象还是指向调用者
function Star() {
     this.sex = '男';
 }
 var ldh = new Star();
 console.log(ldh.sex);

3). 函数

  • 函数不能有重名的参数
  • 函数必须声明在顶层
  • 不允许在非函数的代码块内声明函数。

四、高阶函数

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

        function fn(a, b, callback) {
            console.log(a + b);
            callback && callback();
        }
        fn(1, 2, function() {
            console.log('我是最后调用的');
        });

        $("div").animate({
            left: 500
        }, function() {
            $("div").css("backgroundColor", "purple");
        })

五、闭包

1. 变量作用域

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

2. 闭包

一个作用域可以访问另外一个函数内部的局部变量

主要作用:延伸了变量的作用范围。

        function fn() {
            var num = 10;
            function fun() {
                console.log(num);
            }
            fun();
        }
        fn();
		// fun 这个函数作用域 访问了 另外一个函数fn 里面的局部变量 num
        function fn() {
            var num = 10;
            function fun() {
                console.log(num);
            }
            return fun;
        }
  	  // fn 外面的作用域也可以访问fn 内部的变量s
        var f = fn();
        f();
        function fn() {
            var num = 10;
            return function () {
                console.log(num);
            }
        }
        var f = fn();
        f();

3. 案例

  1. 动态添加属性
var lis = document.querySelector('.nav').querySelectorAll('li');
        for(i = 0; i < lis.length; i++){
            lis[i].index = i;   // 动态添加属性
            lis[i].onclick = function() {
                console.log(this.index);
            }
        }
  1. 利用闭包的方式得到 li 的索引号
for(i = 0; i < lis.length; i++){
            // 利用 for循环创建了四个立即执行函数
            (function(i) {  //必须是通过下面那个括号传递进来的
                // console.log(i);
                lis[i].onclick = function() {
                    console.log(i); // 这个里面的 i是另一个函数里面的内容 在此处产生了闭包 bingo!!
                }
            })(i);
        }

立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的 i 变量

  1. 3秒以后 打印所有 li 元素
        for(var i = 0; i < lis.length; i++){
           (function(i) {
               setTimeout(function() {
                   console.log(lis[i].innerHTML)
               },3000);
           })(i);
        }
  1. 计算打车价格
    • 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格
    • 如果有拥堵情况,总价格多收取10块钱拥堵费
        var car = (function() {
            var start = 13;
            var total = 0;
            return{
                price: function(n) {
                    if(n <= 3){
                        total = start;
                    }
                    else{
                        total = start + (n - 3)*5;
                    }
                    return total;   // 一定要加return返回哦!!
                },   // 正常
                yd: function(flag) {    // 拥堵
                    return flag ? total + 10 : total;   // 拥堵了吗?真的 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

4. 思考题

      var name = "The Window";
      var object = {
          name: "My Object",
          getNameFunc: function() {
              return function() {
                  return this.name;   // 这里的this指向的是这个匿名函数 -> function 立即执行函数 是 window包含的
              };
          }
      };

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

是否有闭包? 无。 没有全局变量

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

是否有闭包? 有 that是局部变量。

六、递归

  • 函数自己调用自己。

  • 和for循环相似

  • 不停开辟新空间 => 栈溢出 => 一定要加退出条件 => return

        function fn() {
            fn();
        }
        fn();

1

        var num = 1;
        function fn() {
            console.log("hello")
            if(num == 6){
                return;
            }
            num++;
            fn();
        }
        fn();

2

1. 解题

1). 递归

        function fn(n) {
            if(n == 1){
                return 1;
            }
            else {
                return n * fn(n-1);
            }
        }
        console.log(fn(3));

2). 斐波那契序列

        function fn(n) {
            if(n == 1 || n == 2){
                return 1;
            }
            else {
                return fn(n-1) + fn(n-2);
            }
        }
        console.log(fn(3));

2. 浅拷贝和深拷贝

1).浅拷贝

  • 只拷贝一层,更深层次对象级别的只拷贝引用
		var obj = {
			id: 1,
			name: 'andy'
		};
		var o = {};
		for(var k in obj) {
			// k 是属性名 obj[k] 是属性值
			o[k] = obj[k];	// 相当于o.k => o.name / o.id
		}
		console.log(o);		// new.html:20 {id: 1, name: "andy"}
  • 更深层次对象也可以拷贝过来 但是他拷贝的是地址

msg是一个对象 会在内存中新开辟一个空间 存上age:18

拷贝的这个地址还是原来obj里面的存的这个数据

两个指向一个数据 如果修改了o里面的数据 相应的也会影响 obj里面的数据

		var obj = {
			id: 1,
			name: 'andy',			
			msg: {
				age: 18		// 更深层次的对象
			}
		};
		var o = {};
		for(var k in obj) {
			// k 是属性名 obj[k] 是属性值
			o[k] = obj[k];	// 相当于o.k => o.name / o.id
		}
		console.log(o);
  • ES6中新增 Object.assign(target,...source) 实现浅拷贝

target:拷贝给谁

source:拷贝那个对象

		var obj = {
			id: 1,
			name: 'andy',			
			msg: {
				age: 18		// 更深层次的对象
			}
		};
		var o = {};
		Object.assign(o,obj);
		console.log(o);

2).深拷贝


		var obj = {
			id: 1,
			name: 'andy',			
			msg: {
				age: 18		// 更深层次的对象
			},
			color: ['red', 'pink']
		};
		var o = {};
		// 使用函数递归 先遍历外面的 再遍历更深层对象里面的
		// 封装函数
		function deepCopy (newobj,oldobj) {
			for(var k in oldobj) {
				//判定属性值是属于简单类型的数据类型还是更深层次的数据类型
				// 1.获取属性值 oldobj[k]
				var item = oldobj[k];
				// 2. 判定数据类型是否是数组
				if(item instanceof Array) {		// 把数组放上面 因为数组也属于对象
					newobj[k] = [];
					deepCopy(newobj[k], item);
				}
				// 3. 判定是否是对象
				else if(item instanceof Object) {	// 这个地方是数据类型 记得大写
					newobj[k] = {};
					deepCopy(newobj[k], item);
				}
				// 4. 都不是 属于简单数据类型
				else{
					newobj[k] = item;
				}
			}
		}
		deepCopy(o, obj);
		console.log(o);

此时再修改o也不会对obj有什么影响