6.6、Closure
6.6.1、闭包引入
需求信息:点击某个按钮,提示"点击的是第n个按钮"
第一种解决方法:将btn所对应的下标保存在btn上
var btns = document.getElementsByTagName('button'); //将btn所对应的下标保存在btn上 for (var i = 0, length = btns.length; i < length; i++) { var btn = btns[i]; btn.index = i; btn.onclick = function () { alert('第' + (this.index + 1) + '个'); } }
第二种解决方法:利用闭包延长局部变量的生命周期
var btns = document.getElementsByTagName('button'); // 利用闭包延长局部变量的生命周期 for (var i = 0, length = btns.length; i < length; i++) { (function (j) { var btn = btns[j]; btn.onclick = function () { alert('第' + (j + 1) + '个'); } })(i); }
6.6.2、闭包概念如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
什么才是闭包?
理解一:闭包是嵌套的内部函数(绝大部分人认为)
理解二:包含被引用变量(函数)的对象(极少部分人认为)
闭包的作用?
它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中
6.6.3、闭包演示
function fun1() { var a = 2; function subFun() { a++; console.log(a); } return subFun; } var f1 = fun1(); f1(); f1(); console.log("==============="); function fun2() { var a = 2; function subFun() { a--; console.log(a); } return subFun; } var f2 = fun2(); f2(); f2(); console.log("===============");
6.6.4、闭包生命周期
生命周期:
- 产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡:在演示说明:
function fn1() { //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了) var a = 2; function fn2() { a++; console.log(a); } return fn2; } var f = fn1(); f(); // 3 f(); // 4 f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)
- 嵌套的内部
6.6.5、闭包应用
闭包应用: 定义JS模块
- 函数成为垃圾对象时就死亡了
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
案例演示:
第一种格式:myModule.js
function myModule() { //私有数据 var msg = 'Hello, World'; //操作数据的函数 function doSomething() { console.log('doSomething() ' + msg.toUpperCase()); } function doOtherthing() { console.log('doOtherthing() ' + msg.toLowerCase()); } //向外暴露对象(给外部使用的方法) return { doSomething: doSomething, doOtherthing: doOtherthing } }
第一种使用:index.html
var module = myModule(); module.doSomething(); module.doOtherthing(); • 1 • 2 • 3
‘第二种格式:myModule.js
(function (window) { //私有数据 var msg = 'Hello, World'; //操作数据的函数 function doSomething() { console.log('doSomething() ' + msg.toUpperCase()); } function doOtherthing() { console.log('doOtherthing() ' + msg.toLowerCase()); } //向外暴露对象(给外部使用的方法) window.myModule = { doSomething: doSomething, doOtherthing: doOtherthing } })(window);
第二种使用:index.html
myModule.doSomething(); myModule.doOtherthing(); • 1 • 2
第七章 JavaScript新特性
7.1、ECMAScript6新特性
7.1.1、let 关键字
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块儿级作用域
- 不存在变量提升
- 不影响作用域链
注意:以后声明变量使用 let 就对了
案例演示:创建四个div,单机每一个div让其变色
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style> .item { width: 100px; height: 50px; border: solid 1px rgb(42, 156, 156); float: left; margin-right: 10px; } </style> </head> <body> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> <!-- 在这里写JavaScript代码,因为JavaScript是由上到下执行的 --> <script> // 获取div元素对象 let items = document.getElementsByClassName('item'); // 遍历并绑定事件 for (let i = 0; i < items.length; i++) { items[i].onclick = function () { // 以前的做法:this.style.background = "pink"; items[i].style.background = "pink"; }; } </script> </body> </html>
7.1.2、const 关键字
const 关键字用来声明常量,const 声明有以下特点:
- 不允许重复声明
- 块儿级作用域
- 声明必须赋初始值
- 值不允许修改
- 标识符一般为大写
注意:声明对象类型使用 const,非对象类型声明选择 let
// 声明常量 const MAX = 100; console.log(MAX); // 对于数组和对象的元素修改, 不算做对常量的修改, 不会报错 const TEAM1 = [1, 2, 3, 4]; const TEAM2 = [1, 2, 3, 4]; // 但是不能修改地址指向 // TEAM2 = TEAM1;
7.1.3、变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式
数组的解构赋值:
//数组的解构赋值 const arr = ["张学友", "刘德华", "黎明", "郭富城"]; let [zhang, liu, li, guo] = arr; console.log(zhang); console.log(liu); console.log(li); console.log(guo);
简单对象的解构赋值:
//对象的解构赋值 const lin = { name: "林志颖", tags: ["车手", "歌手", "小旋风", "演员"] }; let {name, tags} = lin; console.log(name); console.log(tags);
复杂对象的解构赋值:
//复杂对象的解构赋值 let wangfei = { name: "王菲", age: 18, songs: ["红豆", "流年", "暧昧"], history: [ {name: "窦唯"}, {name: "李亚鹏"}, {name: "谢霆锋"} ] }; let {name, age, songs: [one, two, three], history: [first, second, third]} = wangfei; console.log(name); console.log(age); console.log(one); console.log(two); console.log(three); console.log(first); console.log(second); console.log(third);
7.1.4、模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:
- 字符串中可以出现换行符
- 可以使用 ${xxx} 形式输出变量
- 注意:当遇到字符串与变量拼接的情况使用模板字符串
字符串中可以出现换行符:
//定义字符串 let str = `<ul> <li>沈腾</li> <li>玛丽</li> <li>魏翔</li> <li>艾伦</li> </ul>`; console.log(str);
变量拼接:
//变量拼接 let name = '小可爱'; let result = `欢迎${name}访问我的文章`; console.log(result); 1234
7.1.5、简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。
注意:对象简写形式简化了代码,所以以后用简写就对了
let name = "张三"; let age = 18; let speak = function () { console.log(this.name); }; //属性和方法简写 let person = { name, age, speak }; console.log(person.name); console.log(person.age); person.speak();
7.1.6、箭头函数
ES6 允许使用「箭头」(=>)定义函数,通用写法如下:
let fn = (arg1, arg2, arg3) => { return arg1 + arg2 + arg3; }
箭头函数的注意点:
如果形参只有一个,则小括号可以省略
函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
箭头函数 this 指向声明时所在作用域下 this 的值,箭头函数不会更改 this 指向,用来指定回调函数会非常合适
箭头函数不能作为构造函数实例化
不能使用 arguments 实参
省略小括号的情况:
let fn = num => { return num * 10; };
省略花括号的情况:
let fn = score => score * 20;
this 指向声明时所在作用域中 this 的值:
// this 指向声明时所在作用域中 this 的值 let fn = () => { console.log(this); } fn(); let school = { name: "张三", getName() { let subFun = () => { console.log(this); } subFun(); } }; school.getName();
7.1.7、rest 参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments 参数。
注意:rest 参数非常适合不定个数参数函数的场景
// 作用与 arguments 类似 function add(...args) { console.log(args); } add(1, 2, 3, 4, 5); // rest 参数必须是最后一个形参 function minus(a, b, ...args) { console.log(a, b, args); } minus(100, 1, 2, 3, 4, 5, 19);
7.1.8、spread 扩展运算符
扩展运算符(spread)也是三个点(…),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
展开数组:
// 展开数组 let tfboys = ["德玛西亚之力", "德玛西亚之翼", "德玛西亚皇子"]; function fn() { console.log(arguments); } fn(...tfboys);
展开对象:
// 展开对象 let skillOne = { q: "致命打击" }; let skillTwo = { w: "勇气" }; let skillThree = { e: "审判" }; let skillFour = { r: "德玛西亚正义" }; let gailun = {...skillOne, ...skillTwo, ...skillThree, ...skillFour}; console.log(gailun);
7.1.9、Symbol类型
7.1.9.1、Symbol的使用
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型,Symbol 特点如下:
Symbol 的值是唯一的,用来解决命名冲突的问题
Symbol 值不能与其它数据进行运算
Symbol 定义的对象属性不能使用 for…in 循环遍 历 ,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
- 象的所有键名
//创建 Symbol let s1 = Symbol(); console.log(s1); console.log(typeof s1); //添加标识的 Symbol let s2 = Symbol("张三"); let s2_2 = Symbol("张三"); console.log(s2); console.log(s2_2); console.log(s2 === s2_2); //使用 Symbol for 定义 let s3 = Symbol.for("张三"); let s3_2 = Symbol.for("张三"); console.log(s3); console.log(s3_2); console.log(s3 === s3_2); //在方法中使用 Symbol let game = { name: "狼人杀", [Symbol('say')]: function () { console.log("我可以发言") }, [Symbol('zibao')]: function () { console.log('我可以自爆'); } }; console.log(game);
注意:遇到唯一性的场景时要想到 Symbol
7.1.9.2、Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
Symbol.hasInstance演示:
class Person { static [Symbol.hasInstance](param) { console.log("我被用来检测类型了"); } } let o = {}; console.log(o instanceof Person);
Symbol.isConcatSpreadable演示:
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; arr2[Symbol.isConcatSpreadable] = true; console.log(arr1.concat(arr2)); const arr3 = [1, 2, 3]; const arr4 = [4, 5, 6]; arr4[Symbol.isConcatSpreadable] = false; console.log(arr3.concat(arr4));
7.1.10、迭代器
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费,原生具备 iterator 接口的数据:
Array
Arguments
Set
Map
String
TypedArray
NodeList
注意:需要自定义遍历数据的时候,要想到迭代器
工作原理:
创建一个指针对象,指向当前数据结构的起始位置
第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
每调用 next 方法返回一个包含 value 和 done 属性的对象
案例演示:遍历数组
//声明一个数组 const xiyou = ["唐僧", "孙悟空", "猪八戒", "沙僧"]; //使用 for...of 遍历数组 for (let v of xiyou) { console.log(v); } console.log("==============="); //获取迭代器对象 let iterator = xiyou[Symbol.iterator](); //调用对象的next方法 console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
案例演示:自定义遍历数据
//声明一个对象 const banji = { name: "五班", stus: [ "张三", "李四", "王五", "小六" ], [Symbol.iterator]() { //索引变量 let index = 0; let _this = this; return { next: function () { if (index < _this.stus.length) { const result = {value: _this.stus[index], done: false}; //下标自增 index++; //返回结果 return result; } else { return {value: undefined, done: true}; } } }; } } //遍历这个对象 for (let v of banji) { console.log(v); }
7.1.11、生成器
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
7.1.11.1、生成器函数使用
代码说明:
- * 的位置没有限制
- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
- yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
- next 方法可以传递实参,作为 yield 语句的返回值
function * gen() { /*代码1开始执行*/ console.log("代码1执行了"); yield "一只没有耳朵"; /*代码2开始执行*/ console.log("代码2执行了"); yield "一只没有尾巴"; /*代码3开始执行*/ console.log("代码3执行了"); return "真奇怪"; } let iterator = gen(); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log("==============="); //遍历 for (let v of gen()) { console.log(v); }
7.1.11.2、生成器函数参数
function * gen(arg) { console.log(arg); let one = yield 111; console.log(one); let two = yield 222; console.log(two); let three = yield 333; console.log(three); } //执行获取迭代器对象 let iterator = gen('AAA'); console.log(iterator.next()); //next方法可以传入实参 console.log(iterator.next('BBB')); console.log(iterator.next('CCC')); console.log(iterator.next('DDD'));
7.1.11.3、生成器函数实例
案例演示:1s后控制台输出 111,2s后输出 222,3s后输出 333
function one() { setTimeout(() => { console.log(111); iterator.next(); }, 1000) } function two() { setTimeout(() => { console.log(222); iterator.next(); }, 2000) } function three() { setTimeout(() => { console.log(333); iterator.next(); }, 3000) } function * gen() { yield one(); yield two(); yield three(); } //调用生成器函数 let iterator = gen(); iterator.next();
案例演示:模拟获取 ,用户数据 ,订单数据 ,商品数据
function getUsers() { setTimeout(() => { let data = "用户数据"; iterator.next(data); }, 1000); } function getOrders() { setTimeout(() => { let data = "订单数据"; iterator.next(data); }, 1000); } function getGoods() { setTimeout(() => { let data = "商品数据"; iterator.next(data); }, 1000); } function * gen() { let users = yield getUsers(); console.log(users); let orders = yield getOrders(); console.log(orders); let goods = yield getGoods(); console.log(goods); } //调用生成器函数 let iterator = gen(); iterator.next();
7.1.12、Promise
Promise 是 ES6 引入的异步编程的新解决方案,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
7.1.12.1、Promise基本使用
//实例化 Promise 对象 const p = new Promise(function (resolve, reject) { setTimeout(function () { // 成功调用resolve()处理 let data = "数据读取成功"; resolve(data); // 失败调用reject()处理 let err = "数据读取失败"; reject(err); }, 1000); }); //调用 promise 对象的 then 方法 p.then(function (value) { console.log(value); }, function (reason) { console.error(reason); });
7.1.12.2、Promise案例演示
案例演示:
// 接口地址: https://api.apiopen.top/getJoke const p = new Promise((resolve, reject) => { //1. 创建对象 const xhr = new XMLHttpRequest(); //2. 初始化 xhr.open("GET", "https://api.apiopen.top/getJoke"); //3. 发送 xhr.send(); //4. 绑定事件, 处理响应结果 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { //判断响应状态码 200-299 if (xhr.status >= 200 && xhr.status < 300) { //表示成功 resolve(xhr.response); } else { //如果失败 reject(xhr.status); } } } }); //指定回调 p.then(function (value) { console.log(value); }, function (reason) { console.error(reason); });
7.1.12.3、Promise-then方法
调用 then 方法,then 方法的返回结果是 Promise 对象,对象状态由回调函数的执行结果决定,如果回调函数中返回的结果是 非 promise 类型的属性,状态为成功,返回值为对象的成功的值
//创建 promise 对象 const p = new Promise((resolve, reject) => { setTimeout(() => { resolve("用户数据"); }, 1000) }); //链式调用+箭头函数 p.then(value => { console.log(value); return value; }).then(value => { console.log(value); });
7.1.12.4、Promise-catch方法
如果只想处理错误状态,我们可以使用 catch 方法
const p = new Promise((resolve, reject) => { setTimeout(() => { //设置 p 对象的状态为失败, 并设置失败的值 reject("出错啦!"); }, 1000); }); p.catch(function (reason) { console.error(reason); });
7.1.13、Set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
- size:返回集合的元素个数
- add():增加一个新元素,返回当前集合
- delete():删除元素,返回 boolean 值
- has():检测集合中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
//创建一个空集合 let s = new Set(); //创建一个非空集合 let s1 = new Set([1, 2, 3, 1, 2, 3]); //集合属性与方法 //返回集合的元素个数 console.log(s1.size); //添加新元素 console.log(s1.add(4)); //删除元素 console.log(s1.delete(1)); //检测是否存在某个值 console.log(s1.has(2)); //清空集合 console.log(s1.clear());