2.03.03延迟函数,闭包编程,this

2.03.03 延迟函数 闭包 this

1.延迟函数

  1. 延迟函数是在window对象下的函数
  2. 用法:
  • setTimeout 延迟函数
    • 调用者:window
    • 参数:a.匿名函数 b.毫秒
    • 返回值: 数字 1
    • 功能:仅仅执行一次匿名函数
//1.延迟函数  仅仅在设置的时间后执行一次就不会执行了
    var count = 2;
    var d = window.setTimeout(function(){
        count ++ ;
        console. log("count => "+ count);
    },1000)
    console.log(d);//  1
    console.log(typeof d); //number
    //控制台显示:
    //1
    //number
    //count => 3

//2. 使用clearTimeout(d) 或 使用clearTimeout(1) 可以清除上面的延迟函数
    var count = 0;
    var d = window.setTimeout(function(){
        count ++ ;
        console. log("count => "+ count);
    },1000)
    console.log(d);//  1
    console.log(typeof d); //number
    clearTimeout(d) ;
    //控制台显示:
    //1
    //number


//3.拓展 延迟函数加上递归也可以实现定时器函数的功能
    var loop = function(){
        var d = setTimeout(function(){
                console .log("反复执行代码块");
                loop();
            },1000)
    }
    loop();

  1. 延迟函数会改变代码的执行顺序
  • js代码本是从上向下执行的
  • 但加了延迟会改变执行的顺序
  • 延迟函数会最后执行
        console.log(1);
        var t = setTimeout(function(){
            console.log(2);
        },0)
        console.log(3);
        控制台打印:
        1
        3
        2 

        console.log(1);
        var t = setTimeout(function(){
            console.log(2);
        },3000)
        console.log(3);
        控制台打印:
        1
        3
        2 

2.闭包编程

  1. 介绍:闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。我们知道Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。另一方面,在函数外部自然无法读取函数内的局部变量。而闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
    例子:
//  一个函数有权访问另一个函数作用域的变量. (产生封闭的环境)
var outFunc = function () {//外部函数作用域1

    // 变量是私有的通过闭包将变量提供给外部调用

    var num = 0;
    var innerFunc = function () {//内部函数作用域2
        num ++;
        console.log(num)
    }
    return innerFunc;//返回值是个函数
}
            
// 调用outFunc函数,返回innerFunc
var f = outFunc();
// 闭包变量的值是可以保留起来
f();//  控制台输出 1
f();//  控制台输出 2
f();//  控制台输出 3  

var f2=outFunc();
// 闭包变量的值是可以保留起来
f2();//  控制台输出 1
f2();//  控制台输出 2
f2();//  控制台输出 3 
  1. 概念:通过函数字面量创建的函数对象包含一个连接到外部上下文的连接,这被称为闭包,他是JavaScript强大表现力的来源,闭包的好处是内部函数可以访问定义他们的外部函数的参数和变量(除this和argument)。

  2. 闭包的优点

  • 闭包实现函数记忆
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称为记忆
比如说,我们想要一个递归函数来计算 Fibonacci 数列性。一个Fibonacci 数字是之前两个 Fibonacci 数字之和。最前面的两个数字是0和1。
var fibonacci = function (n) {
    return n<2?n:fibonacci(n-1) + fibonacci(n-2);
};

for(var i=0; i<=10; i++) {
    document.writenln('// '+i+':'+ fibonacci(i))
}
// 0:0
// 1:1
// 2:1
// 3:2
// 4:3
// 5:5
// 6:8
// 7:13
// 8:21
// 9:34
// 10:55

这样是可以工作的,但它做了很多无谓的工作。fibonacci 函数被调用了453次。我们调用了11次,而它自身调用了442次去计算可能已被刚计算过的值。如果我们让该函数具备记忆功能,就可以显著地减少运算量

我们在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中。当函数被调用时,这个函数首先检查结果是否已存在,如果已经存在,就立即返回这个结果。

    var outFunc = function (){
    var memo = [0,1];
    var fib = function (n){
        var result =memo[n];
        if (typeof result !== 'number') {
            result = fib(n - 1) + fib(n - 2);
            memo[n]=result;
        }
        return result;
    };

        return fib;
    }

    var fibonacci = outFunc();
这个函数返回同样的结果,但它只被调用了29次。我们调用了它11次,它调用了自己18次去取得之前存储的结果。
  • 闭包构造模块
在普通函数中保存状态,会在运行时带来性能损耗,因为每次执行函数时字面量都会被求值一次。理想的方式是将他放入一个闭包。

var deentityify = (function () {
    // 字符实体表。它映射字符实体的名字到对应的字符
    var entity = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&apos;"
    }

    // 返回 deentityify 方法
    return function(str) {

        // 这才是 deentityify 方法。它调用字符串的 split 方法将字符串分割成数组
        // 遍历数组每一个字符,如果字符能在 entity中的字符实体表中找到
        // 那么就将该字符替换为字符实体替否则返回原字符。
        return str.split("").map(function(char) {
            return entity[char] || char

        }).join("");

    }
})() // 请注意最后一行,我们用()运算法立刻调用刚刚创建的函数,这个调用所创建并返回的函数才是 deentityify 方法
console.log(deentityify('< abc > & Gabbana'))

模块模式利用函数作用域和闭包来创建被绑定的对象与私有成员的关联,在这个例子中。只有 deentityify 方法有权访问字符实体表这个数据对象。

模块模式的一般像是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,后着将他们保存到一个可访问到的地方

利用模块模式就可以摒弃全局变量的使用。他促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效

该模式也可以用来产生安全的对象:
var serial_maker = function () {
    // 返回一个用来产生唯一字符串的对象
    // 唯一字符串由两部分组成:前缀+序列号
    // 该对象包含一个设置前缀的方法,一个设置序列号的方法
    // 和一个产生唯一字符串的 gensym 方法
    var perfix = ''
    var seq = 0
    return {
        set_perfix: function(p) {
           perfix = String(p)  
        },
        set_seq: function(s) {
            seq = s
        },
        gensym: function () {
            var result = perfix + seq
            seq += 1
            return result
        }
    }
}
var seqer = serial_maker()
seqer.set_prefix('Q')
seqer.set_seq(1000)
// 如果我们把 seqer.gensym() 作为一个值传递给第三方函数,那个函数能用它产生唯一字符串,但却不能通过他来改变 prefix 或 seq 的值
var unique = seqer.gensym() // Q1000

seqer 方法没有用到this,因此没办法损害 seqer。
除非调用对应的方法,否则没办法改变perfix 或 seq 的值
seqer 对象是可变的,所以他的方法可能会被修改替换掉,但替换后的方法依然不能访问私有成员
seqer 就是一组函数集合,而且那些函数被赋予特权,拥有使用或修改私有状态的能力
  1. 注意
  • 全局环境的变量,如果没有继续去引用的话,浏览器会自动释放全局变量,没有引用的变量浏览器会自动清除,这是一种垃圾回收机制
  • 但是在闭包中的变量,浏览器没办法去清除,一直占用内存,如果这种变量积累多了,会导致内存不足(内存泄漏)
  • 如果实现一种功能有多种实现方式,不推荐使用闭包。
  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

3.this

1. 概念

  • this 一个指针变量 指向由作用域和调用者来决定
  • 全局变量与在scrip标签下的函数 其实 是在 window对象的一些属性与方法
  • 在全局作用域下,this就是window console.log(this === window); //true
  • 在一个定义的对象下,this就是定义的对象
  • 在函数中除了声明时定义的形式参数,每个函数还接受2个附加的参数:this 和 argument。

2.四种调用模式在如何初始化关键参数this上存在差异

  1. 方法调用模式

    • 当一个函数被保存为对象的一个属性时,我们称其为一个方法。当一个方法被调用时,this被绑定到该对象。
    • 此时方法可以使用this访问自己所属的对象,所以他能从对象中取值对对象进行修改(this到对象的绑定发生在调用的时候,此特性可以使得函数可以对this高度复用。通过this可取得他们所属对象的上下文的方法称为公共方法)
  2. 函数调用模式

    • 当一个函数并不是一个对象的属性时,那么他就是被当做一个函数来调用的,以此模式调用函数时。此时他是window的属性,this会被绑定到全局对象。
    • 注意:这种this的绑定是语言设计上的错误,因为如果语言设计正确,那么当函数被调用时,this应该仍然绑定到外部函数的this变量,这个设计的错误后果就是方法不能利用内部函数来帮助他工作,因为内部函数的this被绑定的错误的值,所以不能共享该方法对对象的访问权
    var flag="全局ok";
    var obj={
        flag:"obj的ok",
        fn:function(val){
            var flag="obj的fn的ok";
            console.log("我是obj的fn,这里的this.flag => " + this.flag);
            //这里有一个函数的自调用
            (
                function(){
                    console.log("我是obj的fn的函数,这里的this.flag => " +this.flag);
                    console.log("我是obj的fn的函数,这里的flag => " +flag);
                    console.log("我是obj的fn的函数,这里的this.val => " +this.val);
                    console.log("我是obj的fn的函数,这里的val => " +val);
                }
            )()

        }

    }
    obj.fn("李子");
    /*     
        控制台打印:
        我是obj的fn,这里的this.flag => obj的ok
        我是obj的fn的函数,这里的this.flag => 全局ok
        我是obj的fn的函数,这里的flag => obj的fn的ok
        我是obj的fn的函数,这里的this.val => undefined
        我是obj的fn的函数,这里的val => 李子
    */
	
  1. 构造器调用模式

    • 介绍:javascript 是一门基于原型继承的语言,所以对象可以直接从其他对象继承属性,单当今大多数语言都是基于类的语言。虽然原型继承极富表现力,但他并未被广泛理解,所以JavaScript提供了一套基于类的语言的构建语法。如果在一个函数前面加上 new 关键字来调用,那么函数将会连接到prototype 成员的新对象,同时this会被绑定到这个新对象上
    • 一个函数如果创建的目的就是希望结合new 前缀来调用,那他就被称为构造器函数。按照约定,他们保存在以大写格式命名的变量里。如果调用构造器函数没有在前面加上new,可能会发生非常糟糕的事情。
    • 常有的情景:就是一个构造函数里面有一个A函数,这个A函数的this指向的是构造函数实例出来的对象,而不是指向构造函数(也可以这么想因为实例出来的对象是new出来的,new会创建对象并修改this的指向,new改变了构造函数里this的指向,A函数当他是指向构造函数的this,因为new的修改,A函数的this也发生了指向的改变,指向了实例出来的对象)
  2. apply调用模式

    • 因为JavaScript是一门函数式的面向对象编程语言,函数被定义为对象,所以函数可以拥有方法,apply方法就是函数自带的方法,apply方法让我们构建一个参数数组传递给调用函数,它允许我们选择this的值,apply方法接受两个参数。第一个是要绑定给this的值,第二个就是一个参数数组
    • 语法:func.apply(thisArg, [argsArray])
      • 参数:thisArg: 在 func 函数运行时使用的 this 值。
      • 请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象。
      • argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。
      • 返回值 指定this值和参数后的func函数调用后的返回值。

3. 改变函数作用域的this指向

  1. call
  • 介绍:call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
  • 调用者:对象里面的函数作为调用者
  • 传参的方式 A对象.函数.call(需要this指向的B对象,参数1,参数2)
  • 语法:
    var objA = {
        foo: function(msg,age){
            console.log("颜色: ",this.color);
            console.log("参数: ",msg,age);
        }
    };

    var objB = {
        color: "red"
    };
    
    objA.foo("hello world",20);
    /* 
    控制台打印:
        颜色:  undefined
        参数:  hello world 20
    */

    objA.foo.call(objB,"hello world",20);
    /* 
    控制台打印:
        颜色:  red
        参数:  hello world 20 
    */
  1. apply
  • apply() 方法使用一个指定的 this 值和一个包含多个参数的数组调用一个函数。
  • 调用者:对象里面的函数作为调用者
  • 传参的方式 A对象.函数.apply(需要this指向的B对象,[参数1,参数2])
  • 语法:
var objA = {
        foo: function(msg,age){
            console.log("颜色: ",this.color);
            console.log("参数: ",msg,age);
        }
    };
    var objB = {
        color: "red"
    };
    objA.foo("hello world",20);
    /* 
    控制台打印:
        颜色:  undefined
        参数:  hello world 20
    */
    objA.foo.apply(objB,["hello world",20]);
    /* 
    控制台打印:
        颜色:  red
        参数:  hello world 20 
    */
  1. bind
  • 调用者:对象里面的函数作为调用者
  • 传参的方式 A对象.函数.bind(需要this指向的B对象)(参数1,参数1)
    var objA = {
        foo: function(msg,age){
            console.log("颜色: ",this.color);
            console.log("参数: ",msg,age);
        }
    }
    var objB = {
        color: "red"
    }
    objA.foo("hello world",20); 
    /* 
    控制台打印:
        颜色:  undefined
        参数:  hello world 20
    */
    objA.foo.bind(objB)("hello world",20);
    /* 
    控制台打印:
        颜色:  red
        参数:  hello world 20 
    */
  • 注意:bind有创建新函数的作用,比如上面的,objA.foo.bind(objB)(“hello world”,20);中的objA.foo.bind(objB)就是创建出了一个新函数,新函数的函数体是与foo一样,但是其中的this指向为objB,然后第二次再加括号(“hello world”,20)是调用这个新函数的意思
  • 基于bind有创建新函数的作用,bind与前两个相比,比较实用,如下面的例子一样
  • bind方法的第一个括号里面还可以添加一个或多个参数,这些参数将作为新函数的参数,供调用时使用。比如,后面学到使得防抖函数支持 event 默认参数:
	    function debounce(func, wait) {
        var timeout; 
        // 返回的函数会作为事件处理函数绑定给对应的监听事件,此时函数会默认接受event事件对象作为参数
        return function (event) {
        
            var context = this;
            
            //这时的func不会被调用,这里只是bind被调用了,创建了一个函数体与func一样的新函数,新函数的this指向与就函数不一样,指向了context,新函数并替换原函数
            //并且event 事件对象通过bind方法传递给防抖函数作为参数。
        
            func = func.bind(context, event)
        
            clearTimeout(timeout)
    
            timeout = setTimeout(func, wait);
        }
    }

    	container.onmousemove = debounce(getUserAction, 1000);

4.容易搞错的一点

  • 变量的寻找是从函数作用域一层一层向外找,this的寻找是找调用该方法的对象
  • 下面给一段伪代码,去分析辨别
	obj.func(){
		var _this=this;//这里的this指向obj
		var timer= setInterval(
		function(){
			this;	
			//这里的this指向window
			//因为这里的function是setInterval里面的函数,而setInterval是window对象下的方法,所以这里的function是函数里面的函数,所以这是函数调用模式,所以this的指向是window
			_this; 
			//这个_this是个变量,他首先在function里面找,找不到,然后到上一层setInterval里面找,还是找不到,然后到func里面找,这时他找到了。
		}
		,300)
		
	}
	
  • 再给一段代码去辨别
<button>
</button>
<script>


    var btn=document.querySelector("button");
    btn.onclick=function(){
        console.log(this); //<button></button>
		//对象里面的函数的this指向对象
		var a="aaa";
        var test=function(){
            console.log(this); //window
            //对象里面的函数的函数的this,是函数调用模式,这里的this指向window
            console.log(a); //aaa
            //这里的a是变量,他就按照按照规则一层一层向外找,在btn.function中找到了
            
        }
        test();

    }

</script>    

5.发现bind的一些问题(高级)

  • func.bind(this,event).bind(this,event).bind(this,event) 等于 func.bind(this,event,event,event)
  • 对比代码,运行代码,看控制台打印,发现问题:
  • 第一版:
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
<div id="container"></div>

<script>
    var count = 1;
    var container = document.getElementById('container');

    function getUserAction(event) {
        container.innerHTML = count++;
        console.log(arguments);
        console.log(event);
        console.log(event.clientX,event.clientY);
    }
   
    function debounce(func,wait){
        var delay=null;
        return function(event){
            var _event=event;
            var _this=this;
            if(delay) clearTimeout(delay);

            var funcc=func.bind(_this,_event);
            delay=setTimeout(funcc,wait);

        }
    }
    
    container.onmousemove=debounce(getUserAction,500);  
</script>
</body>

</html>
  • 第二版:
	<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
<div id="container"></div>



<script>
    var count = 1;
    var container = document.getElementById('container');

    function getUserAction(event) {
        container.innerHTML = count++;
        console.log(arguments);
        console.log(event);
        console.log(event.clientX,event.clientY);
    }
   
    function debounce(func,wait){
        var delay=null;
        return function(event){
            var _event=event;
            var _this=this;
            if(delay) clearTimeout(delay);

            func=func.bind(_this,_event);
            delay=setTimeout(func,wait);

        }
    }
    
    container.onmousemove=debounce(getUserAction,500);  


</script>
</body>

</html>
  • 第三版:
	<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
<div id="container"></div>



<script>
    var count = 1;
    var container = document.getElementById('container');

    function getUserAction(event) {
        container.innerHTML = count++;
        console.log(arguments);
        console.log(arguments[arguments.length-1]);
        console.log(arguments[arguments.length-1].clientX,arguments[arguments.length-1].clientY);
    }
   
    function debounce(func,wait){
        var delay=null;
        return function(event){
            var _event=event;
            var _this=this;
            if(delay) clearTimeout(delay);

            func=func.bind(_this,_event);
            delay=setTimeout(func,wait);

        }
    }
    
    container.onmousemove=debounce(getUserAction,500);  


</script>
</body>

</html>
  • 总结:
  • 第一版
<script>
    function func(a){
        console.log(a);
        console.log(arguments);
    }

    func=func.bind(this,1);
    func();
    /*  控制台打印:
        1
        Arguments [1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    */
    func=func.bind(this,2);
    func();
    /*控制台打印:
        1
        Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    */
    func=func.bind(this,3);
    func();
    /*控制台打印:
        1
        Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    */
    func=func.bind(this,4);
    func();
    /*控制台打印:
        1
        Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    */
</script> 
  • 第二版:
<script>
    function func(a){
        console.log(a);
        console.log(arguments);
    }

    var funcc=func.bind(this,1);
    funcc();
    /* 控制台打印:
        1
        Arguments(1)
    */
    var funcc=func.bind(this,2);
    funcc();
    /* 控制台打印:
        1
        Arguments(1)
    */
    var funcc=func.bind(this,3);
    funcc();
    /* 控制台打印:
        1
        Arguments(1)
    */
    var funcc=func.bind(this,4);
    funcc();
    /* 控制台打印:
        1
        Arguments(1)
    */
</script>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值