zoukankan      html  css  js  c++  java
  • 前端(十三)—— JavaScript高级:回调函数、闭包、循环绑定、面向对象、定时器

    回调函数、闭包、循环绑定、面向对象、定时器

    一、函数高级

    1、函数回调

    // 回调函数
    function callback(data) {}
    // 逻辑函数
    function func(callback) {
        // 函数回调,判断回调函数是否存在
        if (callback) callback(data);
    }
    func(callback);
    
    // 函数回调的本质:在一个函数中(调用函数),当满足一定条件,调用参数函数(回调函数)
    // 回调函数作为调用函数的参数传入,满足一定的条件,调用回调函数,回调函数可以获取调用函数中的局部变量
    // 回调函数目的:通过参数将调用函数内部数据传出,请求数据 => 数据(return | 函数回调) => 外界,匿名函数的自调用,没有调用者,所以无法获取返回值,只能通过回调函数来实现
    
    <!-- 外部要接收数据 -->
    <!-- 1.外部给内部提供回调函数(函数名callback) -->
    <!-- 2.内部将数据反馈给外部回调函数.外部就可以使用内部数据 -->
    <script type="text/javascript">
    	var callback = function (data) {
    		// 使用数据
    		console.log(data);
    	}
    </script>
    
    
    <!-- 请求数据 -->
    <script type="text/javascript">
        // 利用异常处理捕获callback未定义的异常,不做处理
    	try {
    		// 采用匿名函数自调用请求数据,达到页面一加载就获取到数据
    		(function (callback) {
    			console.log("开始请求数据...");
    			// ...
    			var data = [1, 2, 3, 4, 5];
    			console.log("数据请求完毕!!!");
    			// 如果回调函数存在,那么回调对应的函数,并将数据携带出去
    			if (callback) {
    				callback(data);
    			}
    		})(callback)
    		// 请求数据完毕之后,需要让外界获取请求的数据:
    	} catch (err) {
    
    	}
    </script>
    
    回调函数在系统中的使用:
    • 对页面进行点击,点击以后,对外传送数据,数据包括点击的位置
    • 系统已经书写好这种函数的回调,但是没有回调体(回调函数),回调体由普通开发者提供
    • (事件的绑定)钩子:满足系统触发事件的条件时,系统会自动调用回调函数,传出必要的数据
    <script type="text/javascript">
    	// 钩子:满足条件情况下被系统回调的函数(方法),称之为钩子函数(方法) <=> 回调函数
    	document.onclick = function (a, b , c) {
    		console.log("点击事件");
    		console.log(a, b , c);
    	}
    </script>
    

    2、闭包

    function outer() {
        var data = {}
        function inner() {
            return data;
        }
        return inner;
    }
    
    // 使用闭包的原因:不能使用函数回调(调用函数已有固定参数,或不能拥有参数),只能将函数定义到拥有局部变量函数的内部
    // 闭包目的:不允许提升变量作用域时,该函数的局部变量需要被其他函数使用
    // 闭包本质:函数的嵌套,内层函数称之为闭包
    // 闭包的解决案例:①影响局部变量的生命周期,持久化局部变量;②解决循环绑定导致的变量污染
    
    2.1、闭包解决变量的生命周期问题

    局部变量的生命周期在函数运行结束时就结束,将局部变量传到外部函数,实现了延长局部变量声明周期的效果

    <script type="text/javascript">
    	function outer() {
    		// eg: 请求得到的数据,如何不持久化,方法执行完毕后,数据就会被销毁
    		var data = [1, 2, 3, 4, 5];
    		console.log(data);
    		// 通过闭包解决该类问题,所以闭包所以代码均可以随意自定义
    		function inner() {
    			return data;
    		}
    		// 数据被inner操作返回,inner属于outer,属于需要outer将inner返回出去(跟外界建立起联系)
    		return inner;
    	}
    	// 将局部变量生命周期提升于inner函数相同,inner存在,局部变量data就一直存在
    	var inner = outer();
    	console.log(inner());
    
    </script>
    
    2.2、闭包解决变量污染问题

    变量污染:前几次变量的定义,被最后一次定义覆盖。

    // 在循环绑定中,onclick函数中的变量i指向的内存地址,经过循环之后i变成了5,所以每个元素事件运行的时候变量i都是5
    var list = document.getElementById("ulDemo").getElementsByTagName("li");
    for (var i = 0; i < list.length; i++) {
         var li = list[i];
         li.onclick= function () {
             alert(i);
         }
     }
    

    利用闭包解决:

    var lis = document.querySelectorAll('ul li');
    	// 2.循环绑定
    	for (var i = 0; i < lis.length; i++) {
    		// 解决的原理:一共产生了5个外层函数,存储的形参i的值分别是0, 1, 2, 3, 4
    		// 内层函数也产生了5个,且和外层函数一一对应,打印的i就是外层函数的形参i
    
    		// 外层函数
    		(function (i) {
    			// 内层函数:闭包
    			lis[i].onclick = function () {
    				alert(i)
    			}
    		})(i)	
    	}
    	console.log(i);
    

    3.总结:

    • 函数会产生局部作用域, 外部需要使用 -- 返回值 | 函数回调 | 闭包 | 提升作用域(不建议)
    • 在外部另一个函数中使用该局部变量 -- 函数回调 | 闭包

    二、循环绑定

    .html文件
    <ul>
    	<li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
    </ul>
    
    .js文件
    var lis = document.querySelector('li');
    for (var i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
            // 打印列表项的索引
    		console.log(i);
    	}
    }
    // 循环绑定会导致变量污染,解决方法如下:
    // 1.获取局部(块级)作用域解决:在ES5中没有块级作用域,而在ES6中有块级作用域
    // 2.闭包解决,如:一、2.2 利用闭包解决循环绑定所带来的变量污染
    // 3.对象属性解决
    
    2.1、通过获取局部作用域解决变量污染
    // 在ES5中没有块级作用域,在ES6中有块级作用域,所以只要将 var 改为 let 即可解决变量污染
    let lis = document.querySelectorAll('li');
    	for (let i = 0; i < lis.length; i++) {
    		lis[i].onclick = function () {
    			alert(i)
    		}
    	}
    
    2.2、通过对象属性解决变量污染
    <script type="text/javascript">
    	var lis = document.querySelectorAll('li');
    	for (var i = 0; i < lis.length; i++) {
    		// lis[i]依次代表五个li页面元素对象
    		// 给每一个li设置一个(临时)属性
    		lis[i].index = i;  // 第一个li的index属性存储的就是索引0,以此类推,最后一个存储的是索引4
    
    		lis[i].onclick = function () {
    			// var temp = lis[i].index; // lis[i]中的i一样存在变量污染
    			var temp = this.index;  // 当前被点击的li
    			alert(temp)  // temp => this.index(lis[i].index) => i(索引)
    		}
    	}
    
    </script>
    

    三、面向对象JS

    1、属性与方法

    // 创建对象
    var obj = {}; | var obj = new Object();  // 前者一般是创建一个对象是用,后者在需要创建多个对象是用
    // 属性
    obj.属性名 = "";
    // 方法
    obj.func = function () {}
    // 删除属性与方法
    delete obj.prop
    delete obj.func
    

    2、类字典结构使用

    JS中没有字典(键值对存储数据的方式),但可以通过对象实现字典方式存储数据,并使用数据

    • 结构
    var dict = {name: "zero", age: 18}
    
    • 拓展
    // key在JS标识符语法支持情况下,可以省略引号,但key为js标识符不支持的语法情况下,必须添加引号
    var dict = {"my-name": "zero", fn: function () {}, fun () {}}
    
    • 使用
    dict.name | dict["my-name"] | dict.fn()
    
    • 增删改查key与value
    // 增
    dict.key4 = true;
    console.log(dict);
    
    // 删
    delete dict.key4;
    console.log(dict);
    
    // 改
    dict["my-key3"] = [5, 4, 3, 2, 1];
    console.log(dict);
    
    // 查
    console.log(dict.key1);
    console.log(dict["key1"]);
    
    类字典总结:
    • key全为字符串形式,但存在两种书写方式
    • key在JS标识符语法支持情况下,可以省略引号,但key为js标识符不支持的语法情况下,必须添加引号
    • value可以为任意类型
    • 访问值可以通过字典名(对象名).key语法与["key"]语法来访问value
    • 可以随意添加key与value完成增删改查

    3、构造函数(ES5)

    function People(name, age) {
        this.name = name;
        this.age = age;
        this.eat = function () {
            return 'eat';
        }
    }
    

    4、继承(ES5)

    // 父级
    function Sup(name) {
        this.name = name;
        this.fn = function () {
            console.log('fn class');
        }
    }
    // 原型
    console.log(Sup.prototype);
    console.log(sup.__proto__);
    
    // 子级
    function Sub(name) {
        // 继承属性,在继承函数中由被继承(父级)函数调用,传入继承函数的this与被继承函数创建属性对属性进行赋值的所有需要的数据(name)
        Sup.call(this, name);
    }
    // 继承方法,利用prototype原型,将new Sup赋值给Sub.prototype
    Sub.prototype = new Sup;
    // 创建子级对象
    var sub = new Sub("subClass");
    // 使用属性
    console.log(sub.name);
    // 使用方法
    sub.fn();
    
    // 指向自身构造函数
    Sub.prototype.constructor = Sub;
    
    // 案例
    // 1.完成继承,必须拥有两个构造函数
    // 2.一个函数要使用另外一个函数的属性与方法,需要对其进行继承(属性与方法的复用)
    // 3.属性的继承call方法,在继承函数中由被继承函数调用,传入继承函数的this与被继承函数创建属性对属性进行赋值的所有需要的数据,eg:如Sup有name属性,那么可以通过call将Sub的name传给Sup
    // 4.方法的继承prototype原型,将new Sup赋值给Sub.prototype
    function Sup(name) {
    	// 属性存放某个值
    	this.name = name;
    	// 方法完成某项功能
    	this.func = function () {
    		console.log("func");
    	}
    }
    var sup = new Sup("supClass");
    console.log(sup.name);
    sup.func();
    
    // 类似于子级
    function Sub(name) {
    	// 属性的继承
    	Sup.call(this, name);
    }
    // 方法的继承
    Sub.prototype = new Sup;
    
    var sub = new Sub("subClass");
    console.log(sub.name);
    sub.func();
    
    

    5、类及继承(ES6)

    // 父类
    class People {
        // 构造器
        constructor (name, age) {
            this.name = name;
            this.age = age;
        }
        // 实例方法
        eat () {
            console.log('吃吃吃');
        }
        // 类方法
        static create () {
            console.log('诞生');
        }
    }
    // 子类
    class Student extends People {
        constructor (name, age) {
            // super关键词
            super(name, age)
        }
    }
    

    四、定时器

    应用场景:完成JS自启动画;完成CSS无法完成的动画

    • setInterval:持续型定时器,setInterval(函数, 时间间隔(毫秒数), 函数所需参数(可以省略));
    • setTimeout:一次型定时器,setTimeout(函数, 时间间隔(毫秒数), 函数所需参数(可以省略));
    // 一次性定时器
    /* 异步执行
    	setTimeout(函数, 毫秒数, 函数所需参数(可以省略));
    */
    console.log("开始");
    setTimeout(function (data) {
    	console.log(data);
    }, 1000, "数据");
    console.log("结束");
    
    // 持续性定时器
    /*异步执行
    	setInterval(函数, 毫秒数, 函数所需参数(可以省略));
    */
    console.log("又开始");
    var timer = setInterval(function() {     // timer变量,是定时器编号,是普通的数字类型
    	console.log("呵呵");
    }, 1000)
    console.log(timer);
    console.log("又结束");
    
    // 清除定时器
    // 1.持续性与一次性定时器通常都应该有清除定时器操作
    // 2.清除定时器操作可以混用 clearTimeout() | clearInterval()
    // 3.定时器的返回值就是定时器编号,就是普普通通的数字类型
    document.onclick = function () {
    	console.log("定时器清除了");
    	clearInterval(timer);
    	// clearTimeout(timer);
    }
    
    // 清除所有定时器
    // 如果上方创建过n个定时器,那么timeout值为n+1
    var timeout = setTimeout(function(){}, 1);
    for (var i = 0; i < timeout; i++) {
    	// 循环将编号[0, n]所有定时器都清除一次
    	clearTimeout(i)
    }
    
    // 1.允许重复清除
    // 2.允许清除不存在的定时器编号
    
  • 相关阅读:
    深入Log4J源码之Log4J Core
    ScheduledThreadPoolExecutor与System#nanoTime
    []JSR 133 (Java Memory Model) FAQ
    happens-before俗解
    ScheduledThreadPoolExecutor实现原理
    Java Timer&TimerTask原理分析
    Java 编程的动态性,第 1 部分: 类和类装入
    结合反射与 XML 实现 Java 编程的动态性
    Java 日志缓存机制的实现
    Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析
  • 原文地址:https://www.cnblogs.com/linagcheng/p/9799897.html
Copyright © 2011-2022 走看看