10. 闭包
10.1 什么是闭包
-
闭包(closure)指有权访问另一个函数作用域中变量的函数
-
// 闭包:我们 fun 这个函数作用域 访问另外一个函数 fn 里面的局部变量 num // 我们fn 外面的作用域可以访问 fn 内部的局部变量 function fn() { // 变量所在的函数为闭包函数 var num = 10; // function fun() { // console.log(num); // } // fun(); // return fun; return function() { console.log(num); }; } // fn(); var f = fn(); f(); // 10
-
闭包的主要作用:延申了变量的作用范围
-
利用闭包的方式得到 li 的索引号(使用立即执行函数【小闭包】)
// 1. 循环获得(原来的方法) for (var i = 0; i < lis.length; i++){ lis[i].onclick = function() { // console.log(i); 打印的是 4 console.log(this.index); } } // 2. 利用闭包的方式得到当前的 li 的索引号 for (var i = 0; i < lis.length; i++){ // 利用 for 循环创建了 4 个立即执行函数 // 立即执行函数也称为小闭包因为立即执行函数里面的任何一个函数都可以使用它的 i 这个变量 (function(i) { // console.log(i); lis[i].onclick = function(){ console.log(i); } })(i); }
11. 递归
11.1 什么是递归
-
如果一个蛤属在内部可以调用其本身,那么这个函数就是递归函数(类似循环的效果)
-
function fn() { console.log('我要打印6句话'); if (num == 6) { return; } num++; fn(); } fn();
-
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
11.2 利用递归求数学题
-
function fn(n) { if (n == 1) { return 1; } return n * fn(n-1); }
11.3 利用递归求斐波那契数列
-
1、1、2、3、5、8、13、21 (an = an+1 + an-1)
-
function fb(n) { if(n == 1 || n === 2) { return 1; } return fb(n-1) + fb(n - 2); }
11.4 利用递归求:根据 id 返回对应的数据对象
-
var data = [{ id: 1, name: '家电', goods: [{ id: 11, gname: '冰箱', goods: [{ id: 111, gname: '海尔' }, { id: 112, gname: '美的' }, ] }, { id: 12, gname: '洗衣机' }] }, { id: 2, name: '服饰' }]; // 我们想要做输入id号,就可以返回的数据对象 // 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; // 2. 我们想要得里层的数据 11 12 可以利用递归函数 // 里面应该有goods这个数组并且数组的长度不为 0 } else if (item.goods && item.goods.length > 0) { o = getID(item.goods, id); } }); return o; } console.log(getID(data, 1)); console.log(getID(data, 2)); console.log(getID(data, 11)); console.log(getID(data, 12)); console.log(getID(data, 111));
11.5 浅拷贝和深拷贝
-
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(拷贝地址)
-
深拷贝拷贝多层,每一集别的数据都会拷贝
-
Object.assign(target, ... sources)
es6 新增方法可以浅拷贝 -
深拷贝
-
通过递归实现深拷贝
// 封装函数 function deepCopy(newobj, obldobj) { for (var k in oldobj) { // 判断我们的属性值属于哪种数据类型 // 1. 获取属性值 var item = oldobj[k]; // 2. 判断这个值是否是数组 if (item instanceof Array) { newobj[k] = []; deepCopy(newobj[k], item); } else if (item instanceof Object) { // 3. 判断这个值是否是对象 newobj[k] = {}; deepCopy(newobj[k], item); } else { // 4.属于简单数据类型 newobj[k] = item; } } } // 调用 deepCopy(o, obj); console.log(o);
-
loadash / cloneDeep
<body> <script src="lodash.js"></script> <script> var object = [{'a': 1}, {'b': 2}]; var deep = _.cloneDeep(object); </script> </body>
-
通过 JSON.stringify()【把对象转换为 json 字符串】JSON.parse()【把字符串转换为对象】实现
var object = [{'a': 1}, {'b': 2}]; const o = JSON.parse(JSON.stringify(object)); console.log(o);
-
12. 正则表达式
12.1 正则表达式
- 定义:正则表达式是用于匹配字符串中字符组合的模式。在 JS 中,正则表达式也是对象。
- 作用:验证表单(匹配)、替换敏感关键词(替换)、从字符串中获取想要的特定部分(提取)
- 特点:
- 灵活性、逻辑性和功能性非常的强
- 可以迅速地用极简单的方式达到字符串的复杂控制
- 对于刚接触的人来说,比较晦涩难懂。
- 实际开发,一般都是直接复制写好的正则表达式,但是要求会使用正则表达式并且根据实际情况修改正则表表达式
12.2 创建正则表达式
-
通过调用 RegExp 对象的构造函数创建
var 变量名 = new RegExp(/表达式/); var regexp = new RegExp(/123/);
-
利用字面量创建正则表达式
var 变量名 = /表达式/;
12.3 测试正则表达式 test
-
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str)
- regexObj 是写的正则表达式
- str 我们要测试的文本
- 正则表达式里面不需要加引号,不管是数字型还是字符串型
12.4 正则表达式的组成
- 一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/。其中特殊字符也被称为原字符,在正则表达式中是具有特殊意义的专用符号,如 ^、$、+ 等
-
边界符:用来提示字符所处的位置
边界符 说明 ^ 表示匹配行首的文本(以谁开始) $ 表示匹配行尾的文本(以谁结尾) 如果 ^ 和 $ 在一起,表示必须是精确匹配
// 边界符 ^ $ var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型 // /abc/ 只要包含有abc这个字符串返回的都是true console.log(rg.test('abc')); // true console.log(rg.test('abcd')); // true console.log(rg.test('aabcd')); // true console.log('---------------------------'); var reg = /^abc/; console.log(reg.test('abc')); // true console.log(reg.test('abcd')); // true console.log(reg.test('aabcd')); // false console.log('---------------------------'); var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范 console.log(reg1.test('abc')); // true console.log(reg1.test('abcd')); // false console.log(reg1.test('aabcd')); // false console.log(reg1.test('abcabc')); // false
-
字符类:[] 表示有一系列字符可供选择,只要匹配其中一个就可以了
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为 true console.log(rg.test('andy')); // true console.log(rg.test('color')); // true console.log(rg.test('red')); // false var rg = /^[abc]$/; // 三选一 只有是 a 或者只有 b 或者只有 c 这三个字母才返回 true console.log(rg.test('aa')); // true console.log(rg.test('a')); // true console.log(rg.test('b')); // true console.log(rg.test('c')); // true console.log(rg.test('abc')); // false var rg = /^[a-z]$/; // 26个英文字母任何一个字母返回 true console.log(rg.test('a')); // true console.log(rg.test('z')); // false console.log(rg.test(1)); // false console.log(rg.test('A')); // false var rg = /^[a-zA-Z0-9_-]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true console.log(rg.test('a')); // true console.log(rg.test('B')); // true console.log(rg.test(8)); // true console.log(rg.test('-')); // true console.log(rg.test('_')); // true console.log(rg.test('!')); // false var rg = /^[^a-zA-Z0-9_-]$/; // 如果中间括号里面有 ^ 表示取反的意思 千万和 边界符 ^ 别混合 console.log(rg.test('a')); // false console.log(rg.test('B')); // false console.log(rg.test(8)); // false console.log(rg.test('-')); // false console.log(rg.test('_')); // false console.log(rg.test('!')); // true
- [-] 方括号内部 范围符-
- [^] 方括号内部 取反符 ^
-
量词符:用来设定某个模式出现的次数
量词 说明 * 重复零次或更多次(>=0) + 重复一次或更多次(>=1) ? 重复零次或一次(1 || 0) {n} 重复n次 {n,} 重复n次或更多次(>=n) {n,m} 重复n到m次(>=n and <= m) var rg = /^a*$/; console.log(rg.test('')); // true console.log(rg.test('a')); // true console.log(rg.test('aaaaa')); // true var rg = /^a+$/; console.log(rg.test('')); // false console.log(rg.test('a')); // true console.log(rg.test('aaaaa')); // true var rg = /^a?$/; console.log(rg.test('')); // true console.log(rg.test('a')); // true console.log(rg.test('aaaaa')); // false var rg = /^a{3}$/; console.log(rg.test('')); // false console.log(rg.test('a')); // false console.log(rg.test('aaa')); // true var rg = /^a{3,}$/; console.log(rg.test('')); // false console.log(rg.test('a')); // false console.log(rg.test('aaaaa')); // true var rg = /^a{3,5}$/; console.log(rg.test('')); // false console.log(rg.test('a')); // false console.log(rg.test('aaaaa')); // true console.log(rg.test('aaaaaa')); // false var rg = /^[^a-zA-Z0-9_-]{6,16}$/; // 这个模式用户只能输入英文字母数字下划线短横线但是有边界符和[] 又有{}这就限定了出现的次数 // {6,16} 中间不要有空格
-
括号总结
- 大括号:量词符,里面表示重复次数
- 中括号:字符集合,匹配方括号中任意字符(多选1)
- 小括号:表示优先级
-
预定义类:指的是某些常见模式的简写方式
预定类 说明 \d 匹配0-9之间的任一数字,相当于[0-9] \D 匹配0-9以外的字符,相当于 [^0-9]
\w 匹配任意的字母、数字和下划线,相当于 [A-Za-z0-9_]
\W 除所有字母、数字和下划线以外的字符,相当于 [^A-Za-z0-9_]
\s 匹配空格(包括换行符、制表符、空格符),相当于 [\t\r\n\v\f]
\S 匹配非空格的字符,相当于 [^\t\r\n\v\f]
- 汉字正则表达式
/^[\u4e00-\u9fa5]{2,8}$/
- 汉字正则表达式
12.5 正则表达式的替换 replace
-
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式
-
stringObject.replace(regexp/substr,replacement)
- 第一个参数:被替换的字符串或者正则表达式
- 第二个参数:替换为的字符串
- 返回值是一个替换完毕的新字符串
-
正则表达式参数
-
/表达式/[switch]
-
switch(也称为修饰符)按照什么样的模式来匹配,有三种值:
- g:全局匹配
- i:忽略大小写
- gi:全局匹配 + 忽略大小写
text.value.replace(/激情|gay/g,'**);
-
-
13. ES6
13.1 ES6 定义
- ES6 实际上是一个泛指,泛指 ES2015 及后续的版本
13.2 ES6 新增语法
13.2.1 let 新增的用于声明变量的关键字
-
let 声明的变量只在所处于的块级有效
-
防止循环变量变成全局变量
-
if(true) { let a = 10; } console.log(a); // a is not defined for(var i = 0; i < 2; i++) {} console.log(i); // 2 for(let i = 0; i < 2; i++) {} console.log(i); // i is not defined
-
块级作用域:大括号{}产生的作用域
-
注意:使用 let 关键字声明的变量才具有块级作用域,使用 var 声明的变量不具有块级作用域特性。
-
-
不存在变量提升
-
console.log(a); // a is not defined let a = 20;
-
-
暂时性死区
-
var tmp = 123; if(true) { tmp = 'abc'; // 和外部tmp没关系 console.log(tmp); // Cannot access 'tmp' before initialization let tmp; console.log(tmp); // Cannot access 'tmp' before initialization }
-
-
经典面试题:
-
var arr = []; for(var i = 0; i < 2; i++) { arr[i] = function() { console.log(i); } } arr[0](); // 2 arr[1](); // 2 var arr = []; for(let i = 0; i < 2; i++) { arr[i] = function() { console.log(i); } } arr[0](); // 0 arr[1](); // 1
-
13.2.2 const 声明常量,常量就是值(内存地址)不能变化的量
-
具有块级作用域
-
if (true) { const a = 10; } console.log(a); // a is not defined
-
-
声明变量时必须赋值
-
const PI; // Missing initializer in const declaration
-
-
常量赋值后,值不能修改
-
const PI = 3.14; PI = 100; // Assignment to constant variable. const ary = [100. 200]; ary[0] = 'a'; arry[1] = 'b'; console.log(ary); // ['a', 'b'] ary = ['a', 'b']; // Assignment to constant variable.因为改变了其内存的存储地址
-
13.2.3 let、const、var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升现象
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
13.2.4 解构赋值
-
ES6 中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构
-
let ary = [1,2,3]; let [a, b, c, d, e] = ary; console.log(a,b,c); // 1 2 3 console.log(d,e); // undefined undefined
-
对象解构:从数组中或对象中提取值,将提取出来的值赋值给另外的变量。
-
let person = {name: 'zhangsan', age: 20}; let {name, age} = person; console.log(name); // 'zhangsan' console.log(age); // 20 let person = {name: 'lisi', age: 30, sex:'男'}; let {name: myName, age: myAge} = person; // myName myAge 属于别名 console.log(name); // 'lisi' console.log(age); // 30
13.2.5 箭头函数❗❗
-
// (形参) => {结构体} // 调用方式 const fn = () => {} fn()
-
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号(执行结果就是返回值,return也可省;如果形参只有一个,小括号也可省)
function sum(num1, num2) { rreturn num1 + num2; } const num = (num1, num2) => num1 + num2;
-
箭头函数中的 this 关键字
-
箭头函数不绑定 this 关键字,箭头函数中的 this,指向的是函数定义位置的上下文 this
function fn() { console.log(this); // obj return () => { console.log(this); // obj } } const obj = {name: 'zhangsan'}; const resFn = fn.call(obj); // 接收的是匿名函数 resFn();
-
经典面试题
var age = 100; var obj = { // obj对象不能产生作用域 age: 20, say: () => { alert(this.age); // 100 这里的this 是 window } } obj.say(); // undefined
-
13.2.6 剩余参数
-
剩余参数语法允许我们将一个不定数量的参数表示为一个数组
-
function sum(first, ...args) { console.log(first); // 10 console.log(args); // [20,30] } sum(10,20,30)
13.3 ES6 的内置对象扩展
13.3.1 Array 的扩展方法
-
扩展运算符(展开语法)
- 可以将数组或者对象转为用逗号分割的参数序列
let ary = [1,2,3]; ...ary // 1,2,3 console.log(...ary); // 1 2 3
- 扩展运算符可以应用于合并数组。
// 方法一 let ary1 = [1, 2, 3]; let ary2 = [3, 4, 5]; let ary3 = [...ary1, ...ary2]; // [1, 2, 3, 3, 4, 5] // 方法二 ary1.push(...ary2);
- 将类数组或可遍历对象(伪数组)转换为真正的数组
let oDivs = document.getElementSByTagName('div'); oDivs = [...oDivs];
-
构造函数方法:
Array.from()
- 将类数组或可遍历对象转换为真正的数组
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
- 方法还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let arrayLike = { '0': 1, '1': 2, length: 2 }; let newAry = Array.from(arrayLike, item => item * 2); // [2, 4]
-
find() 用于找出第一个符合条件的数组成员,如果没有找到返回 undefined
let ary = [{ id: 1, name: '张三' },{ id: 2, name: '李四' }]; let target = ary.find((item, index) => item.id == 2); console.log(target); // {id: 2,name: '李四'}
-
findIndex() 用于找出第一个符合条件的数组成员的位置,如果没有找到返回 -1
let ary = [1, 5, 10, 15]; let index = ary.findIndex((value, index) => value > 9); console.log(index); // 2
-
includes() 表示某个数组是否包含给定的值,返回布尔值
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false
13.3.2 String 的扩展方法
-
模板字符串
-
ES6新增的创建字符串的方式,使用反引号定义
-
模板字符串中可以解析变量
let name = 'zhangsan'; let sayHello = `hello, my name is ${name}`; // hello, my name is zhangsan
-
模板字符串中可以换行
-
模板字符串中可以调用函数
const sayHello = function() { return '哈哈哈 追不到我吧 我就是这么强大'; }; let greet = '${sayHello()} 哈哈哈哈'; console.log(greet); // 哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
-
-
startWith() 和 endWith()
- startWith() :表示参数字符串是否在原字符串的头部,返回布尔值
- startWith() :表示参数字符串是否在原字符串的尾部,返回布尔值
-
repeat() 方法表示将原字符串重复 n 次,返回一个新字符串
'x'.repeat(3); // 'xxx'
13.3.3 Set 数据结构
-
ES6 提供了新的数据解构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值
-
Set 本身是一个构造函数,用来生成 Set 数据结构
-
Set 函数可以接受一个数组作为参数,用来初始化
const s = new Set(); const set = new Set([1,2,3,4,4]); console.log(set.size); // 4 会过滤掉重复值 // 数组去重 const ary = [...set]; console.log(ary); // [1,2,3,4]
-
实例方法
- add(value):添加某个值,返回 Set 结构本身(可以链式调用)
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
const s = new Set(); s.add(1).add(2).add(3); a.delete(2); s.has(1); s.clear();
-
遍历
- 使用 forEach() 方法
s.forEach(value => console.log(value));
14. 异常处理
14.1 throw 抛异常
function fn(x, y) {
if (!x || !y) {
// throw '没有参数传递进来'
throw new Error('没有参数传递过来')
}
return x + y
}
总结:
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
14.2 try / catch 捕获异常
function fn() {
try {
// 可能发生错误的代码
const p = document.querySelector('.p')
p.style.color = 'red'
} catch(err) {
console.log(err.message)
// 需要加 return 中断程序
}
finally {
// 不管你程序对不对,一定会执行的代码
alert('弹出对话框')
}
}
总结:
- try…catch 用于捕获错误信息
- 将预估可能发生错误的代码写在 try 代码段中
- 如果 try 代码段中出现错误后,会执行 catch 代码段,并接获到错误信息
- finally 不管是否有错误,都会执行
14.3 debugger
类似于打断点,是一个关键字
15. 性能优化
15.1 节流
-
节流:就是指连续触发事件但是在 n 秒中只执行一次函数
-
开发时使用场景——轮播图点击效果、鼠标移动、页面尺寸缩放 resize、滚动条滚动 就可以加节流
<script> const box = document.querySelector('.box') let i = 1 function mouseMove(){ box.innerHTML = ++i // 如果不采用节流,当页面存在大量 dom 的情况,可能会卡顿 } // 节流函数 throttle function throttle(fn, t) { let startTime = 0 return function() { // 得到当前的时间 let now = Date.now() // 判断如果大于等于 500 采取调用函数 if (now - startTime >= t) { // 调用函数 fn() // 起始时间 = 现在的时间 写在调用函数的下面 startTime = now } } } box.addEventListener('mousemove', throttle(mouseMove, 500)) </script> // lodash 实现节流 <sricpt src="./lodash.min.js"></sricpt> box.addEventListener('mousemove', _.throttle(mouseMove, 500))
15.2 防抖
- 防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了时间,则会重新计算函数执行时间
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove(){
box.innerHTML = ++i
// 如果不采用节流,当页面存在大量 dom 的情况,可能会卡顿
}
// 防抖函数 debounce
function debounce(fn, t) {
let timeId
return function() {
// 如果有定时器就清除
if(timeID) clearTimeout(timeId)
// 开启定时器
timeId = setTimeout(function (){
fn()
}, t)
}
}
}
box.addEventListener('mousemove', debounce(mouseMove, 200)
</script>
// lodash 实现防抖
<sricpt src="./lodash.min.js"></sricpt>
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
15.3 总结
- 节流和防抖的区别
- 节流:连续触发事件但是在 n 秒中只执行一次函数,比如可以利用节流实现 1s 之内,只能触发一次鼠标移动事件
- 防抖:如果在 n 秒内又触发了事件,则会重新计算函数执行时间
- 节流和防抖的使用场景
- 节流:鼠标移动,页面尺寸发生变化,滚动条滚动等开销比较大的情况下
- 防抖:搜索框输入,设定每次输入完毕 n 秒后发送请求,如果期间还有输入,则重新计算时间
补充:
两个事件:
- ontimeupdate 事件在视频 / 音频(audio/video)当前的播放位置发送改变时触发
- onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频 / 音频(audio/video)的下一帧时触发
- 获得当前时间 video.currentTime