赞
踩
2023年5月1日晚17:51更新,JS高级知识点全回顾,这是笔者的第三篇推文,写的不好请多多见谅。
从基础知识开始记录,每天一点点回顾,之前很多知识确实忘了一部分,平时也用不到......思来想去,倒不如做一次全盘总结!
一起进步一起加油!大家的关注是对我的最大支持!
全文三万字,为了清晰表述,下方设置了目录模块,前端路上,你我祝好!
目录
1.全局作用域 :
声明在script标签中的,从外部的js引入的 全局变量、函数,都是全局作用域的)
- 局部作用域:
- 函数作用域:声明在函数内部的
- 块作用域:声明在大括号内的,比如if大括号,比如for大括号,用let、const声明的才有块级作用域
- 作用域链:是作用域与作用域嵌套关系,这个关系为变量查询提供依据 。具体依据内容:就近原则
垃圾回收机制(Garbage Collection) 简称 GC
垃圾回收:JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
引用计数:老版本的IE使用这个方案。
浏览器认为:
- 有一个 变量指向对象,则对象的引用次数为 1
- 再有一个变量指向对象,则对象的引用次数为 2
- 如果变量不再指向对象,则对象的引用次数 减 1
- 如果另一个变量不再指向对象,则对象的引用次数 再减 1
- 如果没有任何变量指向对象了,则对象的引用次数为 0,此时垃圾回收器会自动回收这个对象,释放内存。
如果两个对象相互的引用,你用到我了,我用到你了,这样的话,两个对象的引用次数永远都不为0,则永远不会释放,造成内存泄漏。
标记清除:所有现代浏览器都使用这个算法。
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
垃圾回收机制(Garbage Collection) 简称 GC。
垃圾回收:JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
内存泄漏:本应该被释放的内存,由于某种原因没有被释放,叫内存泄漏
算法:引用计数和标记清除,现代浏览器都使用标记清除算法了。
概念一:
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。
概念二:闭包 = 内层函数 + 外层函数的变量
形式上:外函数内部嵌套其他函数,内部函数使用了外函数的局部变量。局部变量 + 内函数构成闭包。
闭包特点:会导致局部变量不会被清除,造成内存泄露,可以通过赋值为null来释放内存。
变量提升:
首先,只有var声明的变量才有提升效果
提升,只是把 声明(var a) 提升到当前作用域的最前面;赋值(a = 10)操作并不会提升
let const的提升问题:
提升,并不是官方的词汇。所以let和const也就谈不上提升不提升。
官方的解释:在进入环境的一刻,变量就会被创建,但是在初始化变量之前,变量是不能被使用的。这就是很多文档中说的 暂时性死区。
官方文档的解释:
let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
用let声明变量,非常简单。就一个原则,先声明,再使用。
函数提升:
函数提升,有的文档叫做函数预定义。
只有用 function 声明的函数,才有函数提升,也会把函数的声明、赋值,提升到当前作用域的最前面(要比变量还要在前面)
有了函数提升,所以,可以在声明函数之前,调用函数。
- function 函数名 (形参, 形参) {
-
- }
-
- // 调用函数
- 函数名(实参, 实参)
形参默认值:
在定义函数的时候,直接给形参赋一个初始的值,这个初始的值就是形参的默认值。
当调用函数的时候,如果没有传入对应的实参,则形参使用默认的值。
- function 函数名 (a, b) {
- console.log(a + b)
- }
-
- // 调用函数
- 函数名() // 调用函数,没有传递实参,则函数中的 a = undefined;b = undefined
- 函数名(10, 20)
-
- /*********************** 为形参设置默认值 ********************************/
- function 函数名 (a = 1, b = 2) {
- console.log(a + b)
- }
-
- // 调用函数
- 函数名() // 调用函数,没有传递实参,则函数中的 a = 1;b = 2
- 函数名(5) // 调用函数,传入了5,则 a = 5; b = 2
- 函数名(10, 20) // 调用函数,传递了实参;则优先使用传入的实参
-
- /***************************** 另类的情况 ********************************/
- function abc(a = 10, b) {
- console.log(a + b)
- }
-
- abc(undefined, 5) // 传入undefined,表示不给a传递实参,让a使用默认的10
剩余参数:
比如有一个需求,定义一个函数,计算一些数字的和。至于几个数字,不确定。所以,调用函数:
- fn(2, 5, 6, 9)
- fn(1, 5, 8)
- fn(1, 7, 2, 9, 0, 6, 9, 10, 23, 4)
- // 剩余参数,是ES6中的新语法
- function fn(a, b, ...c) {
- console.log(a) // 2
- console.log(b) // 5
- console.log(c) // [6, 9, 3, 1] // c 就是剩余的所有实参
- }
- fn(2, 5, 6, 9, 3, 1)
-
-
- // ----------------------------------------------
- function fn(...c) {
- console.log(c) // [2, 5, 6, 9, 3, 1] // c就是剩余的所有实参
- }
- fn(2, 5, 6, 9, 3, 1)
剩余参数 :把一些散乱的实参,聚拢到一起,形成一个真数组
在函数内,有arguments对象,这个对象,只在函数内部才有arguments对象,是一个伪数组,里面就包含了全部的实参
- function fn() {
- /*
- arguments = {
- 0: 2,
- 1: 5,
- 2: 6,
- 3: 9,
- callee: 当前函数,
- length: 4
- }
- */
- let sum = 0
- for (let i = 0; i < arguments.length; i++) {
- sum += arguments[i]
- }
- console.log(sum)
- }
- fn(2, 5, 6, 9)
展开运算符:把数组或对象或字符串,展开,形参单个的值在创建数组的时候,可以将其他数组或字符串展开
展开数组,就是去掉中括号,展开对象,就是去掉大括号
- let arr1 = [2, 3]
- let arr2 = [5, 1]
-
- let arr3 = [...arr1, ...arr2]
- console.log(arr3)
- let arr4 = [...arr1, ...arr2, 9, 0]
- console.log(arr4)
-
- let arr5 = [...'hello'] // ['h', 'e', 'l', 'l', 'o']
- console.log(arr5)
在给函数传递实参的时候,可以将数组或字符串展开
- let arr6 = ['a', 'b', 'c']
- function fn(...argu) {
- console.log(argu) // ['a', 'b', 'c']
- }
- fn(...arr6) // ----> fn('a', 'b', 'c')
-
- // 求数组的最大值
- let arr7 = [3, 8, 2, 7]
- console.log(Math.max( ...arr7 ))
在创建对象的时候,可以将其他对象展开
- let obj1 = { name: 'zs', age: 20 }
- let obj2 = { height: 180, weight: 90 }
-
- let obj3 = { ...obj1, ...obj2 }
- console.log(obj3)
-
- let obj4 = { ...obj1, ...obj2, sex: '男' }
- console.log(obj4)
箭头函数:箭头函数,就是函数的另一种写法。
- () => {}
-
- (形参, 形参) => { 函数体内容 }
-
- let 函数名 = (形参) => {
-
- }
-
- 函数名(实参) // 调用函数
- () 和 => 之间,不能有换行
- => 和 {} 之间,允许有换行
简写格式:
如果形参只有1个,则可以省略掉小括号
如果大括号中只有一行代码,则可以省略掉大括号(如果省略了大括号,则自动return函数体的内容)
- // ---------------------- 简写 ----------------------------
- // 需求:定义一个函数,返回一个数字的平方
-
- // let fn = (n) => {
- // return n * n
- // }
- // console.log(fn(4))
-
- // - 如果形参只有1个,则可以省略掉小括号
- // let fn = n => {
- // return n * n
- // }
- // console.log(fn(5))
-
- // - 如果大括号中只有一行代码,则可以省略掉大括号(如果省略了大括号,则自动return函数体的内容)
- let fn = n => n * n
- console.log(fn(6))
和普通函数的差别:
this指向:
全局中的 this --> window
普通函数中,this --> window
对象.方法() 中,this --> 对象
事件处理函数中,this --> 事件源
箭头函数中的this指向:
- 箭头函数,内部没有 this,如果箭头函数中使用了this,按照作用域链去向上层查找即可。
- 箭头函数,不能当做构造函数(明天讲)
- 箭头函数,内部没有 arguments 对象。如果要获取全部的实参,只能使用 剩余参数。
事件处理函数,用普通的function函数,不要用箭头函数,因为箭头函数内没有this
对象简写格式:
属性简写:如果对象的属性名 和 值 同名,则可以省略一个,只写一个
方法简写:ES6规定,对象的方法,可以 方法名 () {}
- // ---------------- 没有简写的情况 ----------------
- let uname = '佩奇'
- let age = 20
-
- let obj = {
- uname: uname,
- age: age,
- eat: function () {},
- play: function () {}
- }
-
- // ---------------- 简写的情况 ----------------
- let uname = '佩奇'
- let age = 20
-
- let obj = {
- uname,
- age,
- eat () {}, // 这个方法,【不是】箭头函数,只是普通函数的简写格式而已
- play () {}
- }
解:解开、展开
构:结构(数组、对象)
赋值:把一些值 赋值 给 一些变量
解构赋值:把数组或对象展开,把里面的值取出,赋值给一些变量。
没有解构赋值的时候,如果需要把数组的元素赋值给变量,只能这样做:
- let arr = ['刘备', '关羽', '张飞']
-
- // 声明三个变量,a、b、c,值分别是 '刘备', '关羽', '张飞'
-
- let a = arr[0]
- let b = arr[1]
- let c = arr[2]
数组解构赋值:
最基本的语法:
- // ---------------------- 最基本的语法 ------------------------
- // 声明三个变量,a、b、c,值分别是 '刘备', '关羽', '张飞'
- // let [变量] = [数组]
-
- let [a, b, c] = ['刘备', '关羽', '张飞']
- // 上述一行代码,相当于声明了三个变量,let a = '刘备'; let b = '关羽'; let c = '张飞'
变量和值个数不一致的情况:
- // 变量个数 < 值的个数
- let arr = ['刘备', '关羽', '张飞']
- let [a, b] = arr
- console.log(a, b) // 刘备 关羽
-
- // 变量个数 < 值的个数
- let arr = ['刘备', '关羽', '张飞']
- let [a, b, c, d] = arr
- console.log(a, b, c, d) // 刘备 关羽 张飞 undefined
-
- // 可以设置变量的默认值
- let [a, b, c, d = '赵云', e = '马超'] = ['刘备', '关羽', '张飞']
- console.log(a, b, c, d, e)
按需取值:
- let arr = ['刘备', '关羽', '张飞']
- let [a, , b] = arr
- console.log(a, b) // 刘备 张飞
多维数组解构:
- // -------------------------- 多维数组解构 ----------------------------------
- // let arr = [] // 一层 [] 就是 一维数组
- // let arr = [['旺财', 2], ['小花', 3]] // 两层 [] 就是二维数组
-
- let [[a, b], [c, d]] = [['旺财', 2], ['小花', 3]]
- console.log(a, b, c, d) // 旺财 2 小花 3
练习:
- // ------------------------- 练习1 --------------------------------------
- // let [a, [b, c, d], e, [f, g], h, i] = [22, [44, 55, 11], 33, [66, 77], 88, 99]
- // console.log(a, h) // 22 88
-
- // ------------------------- 练习2:交换两个变量的值 -----------------------
- let x = 10
- let y = 20
- ;[y, x] = [x, y] // []如果作为语句的开头,则【前面】【必须】加分号
- console.log(x) // 20
- console.log(y) // 10
- /************************ 最基本的语法格式 **************************/
- // 【前面的变量名,必须是对象的键名】
- // 【变量的顺序无所谓,随便写,对象的解构和顺序无关】
- let { 键1, 键2, 键3 } = { 键1: 值1, 键2: 值2, 键3: 值3 }
- let { 键2, 键1, 键3 } = { 键1: 值1, 键2: 值2, 键3: 值3 }
-
- let { uname, age, sex } = { uname: 'zs', age: 20, sex: '男' }
-
- let { sex, uname, age } = { uname: 'zs', age: 20, sex: '男' }
- console.log(sex) // 男
- console.log(uname) // zs
- console.log(age) // 20
变量的数量和值的数量不一致:
- 变量个数 < 值的个数
- let { sex, uname } = { uname: 'zs', age: 20, sex: '男' }
- console.log(uname, sex)
-
- 变量个数 > 值的个数
- let { sex, uname, age, height, weight } = { uname: 'zs', age: 20, sex: '男' }
- console.log(uname, sex, age, height, weight)
-
- 可以给变量设置默认值
- let { sex, uname, age, height = 180, weight = 100 } = { uname: 'zs', age: 20, sex: '男' }
- console.log(uname, sex, age, height, weight)
给变量起新的名字:
- let age = 80
- let uname = '李四'
-
- let { sex, uname: username, age: nl } = { uname: 'zs', age: 20, sex: '男' }
- console.log(sex, username, nl)
多级对象解构:
- let person = { uname: '狗哥', age: 45, cat: { cname: '小花', cage: 2 } }
- let { uname, age, cat: { cname, cage } } = person
- console.log(uname, cname, age, cage) // 狗哥 小花 45 2
- <ul>
- <li data-id="0">aaaaaaaaaa</li>
- <li data-id="1">aaaaaaaaaa</li>
- <li data-id="2">aaaaaaaaaa</li>
- </ul>
-
- <script>
- // 使用事件委托的方案,给每个 li 添加click事件;点击的时候,输出 data-id 的值
- let ul = document.querySelector('ul')
- ul.addEventListener('click', function (e) {
- // 把我们需要的 tagName 和 dataset 解构出来
- // console.dir(e.target) // { aa: '', bb: '', tagName: '', dataset: {}, cc: '', dd: '' }
- let { tagName, dataset } = e.target
-
- // let { uname, age } = { uname: 'zs', age: 20 }
- // console.log(uname)
-
- if (tagName === 'LI') {
- console.log(dataset.id)
- }
- })
- </script>
为函数传递参数解构:
- <ul>
-
- </ul>
- <script>
-
- // 在调用函数,传递参数的时候,实际上
- // 相当于 let 形参 = 实参
- // let a = { uname: 'zs', age: 20 }
-
- // let { uname, age } = { uname: 'zs', age: 20 }
-
- // function fn({ uname, age }) {
- // console.log(uname)
- // console.log(age)
- // }
-
- // fn({ uname: 'zs', age: 20 })
-
- // ------------------------- 遍历展示数据 ---------------------
- let arr = [
- { uname: '宋江', nickname: '及时雨' },
- { uname: '吴用', nickname: '智多星' },
- { uname: '林冲', nickname: '豹子头' },
- ]
-
- let newArr = arr.map(({ uname, nickname }) => {
- // console.log(item)
- return `<li>${uname} - ${nickname}</li>`
- })
-
- document.querySelector('ul').innerHTML = newArr.join('')
-
- </script>
filter - 筛选,这个方法的作用是:根据给出的条件,筛查出符合条件的元素。
语法:和map一模一样。
- let arr = [2, 5, 7, 8, 3, 6]
-
- let 筛选后的新数组 = arr.filter(function (item, index) {
- // return 条件
- return item > 5
- })
-
- // --------------------------- 语法练习 ---------------------------
- let arr = [2, 5, 7, 8, 3, 6]
- let newArr = arr.filter(function (item) {
- return item > 5
- })
- console.log(newArr) // [7, 8, 6]
-
- <button>低于2元</button>
- <button>2~4元</button>
- <button>4元以上</button>
- <button>全部商品</button>
- <ul>
- <!-- <li>方便面 - 2.5</li> -->
- </ul>
-
- <script>
- let goods = [
- { goodsname: '方便面', price: 2.5 },
- { goodsname: '火腿肠', price: 2 },
- { goodsname: '卤蛋', price: 1.5 },
- { goodsname: '泡椒凤爪', price: 5 },
- { goodsname: '辣条', price: 3.5 },
- ]
-
- // 封装渲染函数
- function render(list) {
- let res = list.map(function (item) {
- return `<li>${item.goodsname} - ${item.price}</li>`
- })
- document.querySelector('ul').innerHTML = res.join('')
- }
-
- render(goods) // 页面刷新后,先调用一次函数,默认展示全部商品
-
- // 找到全部按钮,循环添加事件
- let btns = document.querySelectorAll('button')
- for (let i = 0; i < btns.length; i++) {
- btns[i].addEventListener('click', function () {
- let temp = goods
- if (i === 0) {
- // temp = '低于2元的商品'
- // temp = goods.filter(function (item) {
- // return item.price < 2
- // })
- temp = goods.filter(item => item.price < 2)
- } else if (i === 1) {
- // temp = '2~4元的商品'
- temp = goods.filter(item => item.price >= 2 && item.price <= 4)
- } else if (i === 2) {
- // temp = '4元以上的商品'
- temp = goods.filter(item => item.price > 4)
- } else {
- temp = goods
- }
- render(temp) // 渲染的是:筛选后的商品
- })
- }
- </script>
- let obj = {} ----- 字面量方式,创建对象
- let obj = new Object() -----
- let obj = new 构造函数() ------ 通过构造函数方式创建对象
下面介绍构造函数方式创建对象:
- // 构造函数
- // 形式上,为了和普通函数区分,建议 构造函数首字母大写
- function Pig(u, a, s) {
- // 构造函数内部,有一个this;这个this表示创建好的对象,比如 peiqi、qiaozhi....
- // 对象.属性 = 值
- this.uname = u
- this.age = a
- this.sex = s
- }
- // Pig() // 直接调用函数,Pig就是普通函数
- // 通过构造函数创建对象
- // let 对象 = new 构造函数()
- let peiqi = new Pig('佩奇', 2, '女') // 如果new Pig() ,那么这个 Pig 就是构造函数
- // peiqi = { uname: '佩奇', age: 2, sex: '女' }
-
- let qiaozhi = new Pig('乔治', 1, '男')
- // qiaozhi = { uname: '佩奇', age: 1, sex: '男' }
- // let zhubaba = new Pig()
- // let zhumama = new Pig()
- console.log(peiqi) // { uname: '佩奇', age: 2, sex: '女' }
- console.log(qiaozhi) // { uname: '佩奇', age: 1, sex: '男' }
通过 new 构造函数() 创建一个对象,这个过程,叫做实例化。最后创建好的对象,叫做实例对象。
new实例化的过程:
通过 new 构造函数() 创建一个对象,这个过程,叫做实例化。最后创建好的对象,叫做实例对象。
那么在new一个构造函数的时候,发生了什么?
- function Pig(u, a, s) {
- this.uname = u
- this.age = a
- this.sex = s
- }
-
- let peiqi = new Pig('佩奇', 2, '女')
- 构造函数内,会创建一个空对象 {}
- 让 this 指向 空对象 this 就是 空对象了
- 函数内代码执行,给this加属性;就是空对象加属性,结果 { uname: '佩奇', age: 2, sex: '女' }
- 返回创建好的对象 return { uname: '佩奇', age: 2, sex: '女' }
- 最后:let peiqi = 函数中返回的对象,也就是 { uname: '佩奇', age: 2, sex: '女' }
- function Pig (u, a, s) {
- this.uname = u
- this.age = a
- this.sex = s
- }
-
- // 直接给构造函数也可以加属性,这类属性,叫做静态属性
- Pig.height = 20
- Pig.eat = function () {
- console.log(123)
- }
-
- // 实例化,得到peiqi,peiqi就是实例对象
- let peiqi = new Pig('佩奇', 2, '女')
-
- // uname、age、sex 是实例对象 peiqi 的属性
- // 所以,uname、age、sex就叫做实例成员
构造函数中,给this添加的成员,就是实例成员
实例成员,只能由实例对象去调用
console.log(peiqi.uname)
cosole.log(peiqi.age)
直接给构造函数添加的成员,就是静态成员
静态成员,只能由构造函数去调用
console.log(Pig.height)
Pig.eat()
谁的成员,谁调用,Math.PI / Math.random() / Math.floor() / Date.now() 都是静态成员。
- // 使用字符串调用属性 和 方法
- 'hello'.length
-
- 'hello'.substring(2)
-
- // 使用数字调用方法
- let num = 5.1293823872474
- num.toFixed(2) // 5.13
-
- // 使用正则表达式调用方法
- /^\d{6}$/.test('123456') // true
-
- // 使用函数调用方法
- Math.random()
对象调用属性、或调用方法的语法是:对象.属性 对象.方法()
当我们使用字符串 或 数字 ...... 去调用一个方法或属性的时候,系统会自动将字符串、数字给我们包装成对象,然后再去调用属性或方法。
举例:
- 'hello'.substring(2)
-
- // 实际上,内部是这样实现的
-
- // 1. 实例化一个字符串对象,参数就是 hello
- let obj = new String('hello')
-
- // 2. 用对象调用方法
- obj.substring(2)
Object.assign() --- 拷贝(复制)一个对象
- // 1. Object.assign() --- 拷贝(复制)一个对象
-
- let obj1 = { name: '张三', age: 20 }
-
- // 语法一:
- // let obj2 = Object.assign({}, obj1)
- // obj2.age = 100 // 尝试修改一个对象
- // console.log(obj1, obj2)
-
- // 语法二:
- let obj2 = {}
- Object.assign(obj2, obj1)
- obj2.age = 200 // 尝试修改其中一个对象
- console.log(obj1, obj2)
Object.keys() --- 获取到对象所有的键,得到数组
Object.values() --- 获取对象所有的值,得到数组
- let obj = {
- uname: 'zs',
- age: 20,
- sex: '男'
- }
-
- // 获取对象所有的键
- let jian = Object.keys(obj)
- console.log(jian) // ['uname', 'age', 'sex']
-
- // 获取对象所有的值
- let zhi = Object.values(obj)
- console.log(zhi) // ['zs', 20, '男']
曾经学过的数组方法:push、pop、shift、unshift、splice、forEach、map、join、filter
some -- 判断数组的元素是否有符合条件的,如果有则得到true;否则得到false
some 一些,所以,数组中只要有一些元素符合条件,就得到true。
- // some ------------------------------------------------
- let arr = [1, 5, 6, 8]
- let result = arr.some(function (item, index) {
- // return '条件'
- return item > 5 // 查看数组的元素,是否有大于5的
- })
-
- console.log(result) // true
every -- 判断数组的每一个元素是否都符合条件,如果都符合条件则得到true,否则得到false
every 每一个,所以,要求数组的每一个元素都要符合条件,才能得到true
- // every ------------------------------------------------
- let arr2 = [1, 5, 6, 8]
- let result2 = arr2.every(function (item, index) {
- // return '条件'
- return item > 0 // 要求数组的每一个元素都大于0
- })
- console.log(result2) // true
Array.from() --- 把伪数组 转成 真数组 【转成真数组之后,就可以调用数组方法了】
find --- 根据条件,查找数组元素;如果找到则返回找到的第1个元素;如果没找到则返回undefined
- let arr = [1, 5, 6, 8]
-
- // find -------------------------------------------------------
- let result = arr.find(function (item, index) {
- // return '条件'
- return item > 5 // 在数组中查找 大于 5 的元素
- })
- console.log(result) // 6
findIndex --- 根据条件,查找数组元素的下标;如果找到则返回符合条件的第1个元素的下标;没找到则返回 -1
- let arr = [1, 5, 6, 8]
-
- // findIndex -------------------------------------------------------
- let result2 = arr.findIndex(function (item, index) {
- // return '条件'
- return item > 5 // 在数组中查找 大于 5 的元素的【下标】
- })
- console.log(result2) // 2
reduce --- 累加得到一个值(比如把商品的总价格计算出来)
- let arr = [1, 5, 6, 8]
-
- // reduce 语法一:
- let result = arr.reduce(function (prev, item) {
- return prev + item
- })
- console.log(result) // 20
- /*
- 第1次循环prev是数组的第1个元素,后续 prev = 上次运算的结果
- 第1次循环: prev = 1 item = 5 返回 1+5 = 6, 此时result就是6
- 第2次循环: prev = 6 item = 6 返回 6+6 = 12,此时result就是12
- 第3次循环: prev = 12 item = 8 返回 12+8 = 20,此时result就是20
- */
-
- // reduce 语法二:可以设置初始值
- // let result2 = arr.reduce(函数, 初始值)
- let result2 = arr.reduce(function (prev, item) {
- return prev + item
- }, 100)
-
- console.log(result2) // 120
- /*
- 因为有初始值,则 prev开始的时候就是初始值、item就是数组的第1个元素
- 第1次循环: prev = 100 item = 1 返回 100+1 = 101,此时result2 就是 101
- 第2次循环: prev = 101 item = 5 返回 101+5 = 106,此时result2 就是 106
- 第3次循环: prev = 106 item = 6 返回 106+6 = 112,此时result2 就是 112
- 第4次循环: prev = 112 item = 8 返回 112+8 = 120,此时result2 就是 120
- */
reverse --- 翻转数组;把数组的元素翻转过来
- let arr = [1, 5, 6, 8]
-
- let arr2 = arr.reverse()
-
- console.log(arr2) // [8, 6, 5, 1]
- substring() ---- 字符串截取
- trim() --- 去掉字符串 两边 的空白
- split() --- 把字符串转成数组【和数组的join刚好相反】
- startsWith --- 判断字符串的开头是什么
- endsWith --- 判断字符串的结尾是什么
- includes --- 判断字符串中是否包括某个字符,包含返回true,不包括返回false
- indexOf --- 判断字符串中是否包括某个字符,包括返回字符的位置,不包括返回 -1
- replace --- 字符串替换
- toUpperCase() --- 把字符串中的字母转成大写
- toLowerCase() --- 把字符串中的字母转成小写
- match --- 获取和正则匹配的结果
- toFixed() --- 保留几位小数
- parseInt() --- 转成整数,parseInt()可以直接调用;
- ES6之后,把它放到Number对象,可以 Number.parseInt()
- 实际开发,两个写法都可以使用。
- parseFloat() --- 转成小数
概念:
●面向过程
○面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了
○面向过程,就是按照我们分析好了的步骤,按照步骤解决问题
●面向对象
○面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
○面向对象是以对象功能来划分问题,而不是步骤。
面向对象特点:
●封装
●继承
●多态
优缺点比较:
●面向过程
○优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
○缺点:不灵活、复用性较差
○小结:面向过程性能高,但是不灵活,复用性低
●面向对象
○优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
○缺点:性能比面向过程低
○小结:面向对象灵活、易扩展、易维护,但是性能比面向过程低
生活离不开蛋炒饭,也离不开盖浇饭,选择不同而已,只不过前端不同于其他语言,面向过程更多
构造函数创建对象
- function Person(u, a) {
- // 在构造函数中,为实例对象(this)添加属性、方法
- this.uname = u
- this.age = a
- this.say = function () {
- console.log('会说话')
- }
- this.eat = function () { }
- this.cook = function () { }
- }
-
- // 实例化,创建一个对象;所以zs / xh就是实例对象
- let zs = new Person('张三', 20)
- let xh = new Person('小花', 18)
-
- console.log(zs)
- console.log(xh)
-
- // 验证一下,zs.say === xh.say
- console.log(zs.say === xh.say) // false
问题:每创建一个对象,都会单独存各自的方法。实际上这些方法都是一样的,没必要单独存。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
找Person的原型对象 :Person.prototype
- function Person(u, a) {
- // 在构造函数中,为实例对象(this)添加属性、方法
- this.uname = u
- this.age = a
- // 方法不要加给 this(实例对象) 了。
- }
-
- // 方法,应该加给构造函数的原型对象
- console.log(Person.prototype) // { constructor: ƒ }
- Person.prototype.say = function () {
- console.log('会说话')
- }
-
- // 实例化,创建一个对象;所以zs / xh就是实例对象
- let zs = new Person('张三', 20)
- let xh = new Person('小花', 18)
-
- console.log(zs)
- console.log(xh)
-
- // // 验证一下,zs.say === xh.say
- console.log(zs.say === xh.say) // true
-
- zs.say()
- xh.say()
构造函数和原型对象方法中的this指向实例对象且不要用箭头函数。
上图描述了:所有的数组,都是 Array的实例对象,所以,都可以调用原型对象中的方法。
所以,要为数组扩展方法,就给 Array.prototype 加方法即可。
- let arr = [3, 2, 8, 5, 9, 3, 0, 1]
-
- let arr2 = [4, 5, 6, 2, 3, 4, 5]
-
- // Array.prototype.属性 = 值
- Array.prototype.max = function () {
- // 函数中,数组是谁? ----> this
- // console.log(this)
- // Math.max(...数组)
- return Math.max(...this)
- }
-
- console.log(arr)
-
- console.log(arr.max())
- console.log(arr2.max())
constructor属性:
●每个原型对象里面都有个constructor 属性,该属性指向该原型对象的构造函数
●如果修改了原型对象,则必须设置constructor属性,值就是构造函数。
__proto__属性:
●实例对象.__proto__ 指向 原型对象。
下图重要,尽量理解
- function Person(u, a) {
- this.uname = u
- this.age = a
- }
-
- // 给Person的原型对象加方法
- Person.prototype = {
- say() { },
- eat() { },
- cry() { },
- constructor: Person // 修改了原型对象后,必须指定constructor
- }
-
- // 实例化
- let zs = new Person('张三', 20)
概念:一个对象,它有原型对象,原型对象也有原型对象,则这样的多个对象形成一个链式的结构,这个链式的结构称为原型链。
-
- // 构造函数
- function Person() {
-
- }
- // 实例化对象
- let zs = new Person()
-
- // console.log(zs) // 输出到浏览器之后,展开查看原型对象
-
- console.log(zs.__proto__) // 找Person的原型对象
- console.log(zs.__proto__.__proto__) // Object的原型对象
- console.log(zs.__proto__.__proto__.__proto__) // null
意义:原型链为对象成员查找机制提供一个方向,或者说一条路线,【就近原则】。
●查找对象的属性,比如 zs.age
○优先在当前对象自身查找,有没有这个属性
○去原型对象上去查找 age 属性
○去原型对象的原型对象上去查找
○..............
○如果找到最顶层,还没有找到,则 得到 undefined。
●查找对象的方法,比如 zs.say()
○优先在当前对象自身查找,有没有这个方法
○去原型对象上去查找 say 方法
○去原型对象的原型对象上去查找
○..............
○如果找到最顶层,还没有找到,则 报错 zs.say is not a function。
- // 构造函数
- function Person() {
- // this.age = 20 // 给 实例对象 添加 age 属性
- }
-
- Person.prototype.age = 100 // 给原型对象 添加 age 属性
- // 实例化对象
- let zs = new Person()
-
- console.log(zs.age) // 输出 实例对象的 age 属性
用来检测构造函数.prototype 是否存在于实例对象的原型链上
语法:对象 instanceof 构造函数
●返回true,说明对象的原型链上,出现了构造函数的prototype
●返回false,说明对象的原型链上,没有出现构造函数的prototype
- function Person() {
-
- }
- let zs = new Person()
-
- // instanceof运算符:查看构造函数的prototype 是否出现在了 实例对象的原型链上
-
- // console.log(对象 instanceof 构造函数)
- console.log(zs instanceof Person) // true
- console.log(zs instanceof Array) // false
- console.log(zs instanceof Object) // true
作用:用于检查对象的类型,比如检查对象 是 数组、对象呢?
- function getType(a) {
- // console.log(typeof a)
- if (a instanceof Array) {
- // 说明 a 是数组
- console.log('array')
- } else if (a instanceof Object) {
- console.log('object')
- } else {
- console.log('其他类型')
- }
- }
-
- getType([]) // array
- getType({}) // object
- getType(null)
-
- // ---------------------------- 如果要判断是否是数组,用下面的方法是最好的 ------------------
- // console.log(Array.isArray([])) // true
- // console.log(Array.isArray('')) // false
- // console.log(Array.isArray({})) // false
- // console.log(Array.isArray(null)) // false
原型继承:对象(当前的) --> 原型对象(爸) --> 原型对象(爷)
当前对象,可以使用它爸的属性和方法;也可以使用它爷爷的属性或方法,这就是原型继承
- function Father() {
- this.car = '宝马'
- this.money = '100w'
- }
- let f = new Father()
- // console.log(f)
-
- function Son() {
-
- }
- // 设置Son的原型对象为 f
- Son.prototype = f // 实现了Son继承Father
- Son.prototype.contructor = Son // 这一行不影响最终结果,最好写上
-
- let s = new Son()
- console.log(s)
- console.log(s.car)
- console.log(s.money)
-
- // 1. 定义构造函数
- function Modal(title = '标题', content = '内容') {
- this.title = title
- this.content = content
- this.box = null
- }
-
- // 2. 创建 div 的方法,定义到原型对象上
- // 显示模态框的方法
- Modal.prototype.show = function () {
- // 先判断一下,页面中有没有模态框,如果已经存在模态框了,先把它移除
- document.querySelector('.modal')?.remove()
-
- // 创建div
- this.box = document.createElement('div')
- this.box.classList.add('modal')
- this.box.innerHTML = `
- <div class="header">${this.title} <i>x</i></div>
- <div class="body">${this.content}</div>
- `
- // 把div放到body
- document.querySelector('body').append(this.box)
- // 先把div放到页面中
- // 才能找到关闭按钮
- // box.querySelector('i') ----> 在 box 内部,查找 i 标签
- this.box.querySelector('i').addEventListener('click', () => {
- // box.remove()
- // 调用 close 方法
- this.close() // 外层改为箭头函数,这样的话,this就表示外层作用域中的this了。就是实例对象了
- })
- }
-
- Modal.prototype.close = function () {
- // console.log(123)
- this.box.remove()
- }
-
- // 3. 测试:点击删除,实例化对象。调用方法。
- document.querySelector('#delete').addEventListener('click', function () {
- let m = new Modal('提示你啊', '你确定要删除吗?')
- m.show()
- })
●拷贝针对的是对象类型
●拷贝不是赋值,【不是】 let obj2 = obj1
●拷贝,就是要完全的创建出一个新对象
浅拷贝
只拷贝对象的第一层属性和值。
- let obj1 = {
- uname: '狗哥',
- age: 45,
- info: ['165cm', '100kg'],
- cat: {
- cname: '小花',
- cage: 2
- },
- say: function () {
- console.log(123)
- }
- }
-
- // 2. ------------------------- 浅拷贝 -----------------------------
- // 只拷贝对象第1层的键值对,拷贝给另一个对象
- // 2.1 【let obj2 = Object.assign({}, obj1)】 【let obj2 = {}; Object.assign(obj2, obj1)】
- let obj2 = Object.assign({}, obj1)
- obj1.age = 100 // 尝试修改一个对象的属性,看另一个对象是否改变了
- obj1.cat.cage = 50
- console.log(obj1, obj2)
-
- // 2.2 let obj2 = { ...obj1 }
- let obj2 = { ...obj1 }
- obj1.age = 200
- obj1.cat.cage = 20
- console.log(obj1, obj2)
逐层拷贝对象的属性和值,使两个对象能够完全分开(修改一个对象的任何属性,不会影响另一个对象)。
- <script src="./lodash.min.js"></script>
- <script>
- let obj1 = {
- uname: '狗哥',
- age: 45,
- info: ['165cm', '100kg'],
- cat: {
- cname: '小花',
- cage: 2
- },
- say: function () {
- console.log(123)
- }
- }
- // 不仅仅拷贝一层,逐层的拷贝,最终使得两个对象完全的分开
- // 3.1 先把对象转成字符串,再转成对象 【原对象没有函数、完全可以使用这个方案】
- let obj2 = JSON.parse(JSON.stringify(obj1))
- obj1.age = 1000
- obj1.cat.cage = 500
- console.log(obj1, obj2)
-
- // 3.2 使用插件提供的方法,常用的方法 lodash库 里面的 _.cloneDeep()
- let obj2 = _.cloneDeep(obj1)
- obj1.age = 2000
- obj1.cat.cage = 1500
- console.log(obj1, obj2)
- </script>
函数内,调用自己,这样的函数就是递归函数。
必须设置一个条件,来终止调用,否则会形成死循环。
递归输出1-10
- // 函数内部,调用自己,这就是递归函数
- // 递归函数,必须加一个终止条件,否则就是死循环
- let i = 1
- function fn() {
- if (i > 10) {
- return // 终止函数执行
- }
- console.log(i)
- i++
- fn()
- }
-
- fn()
计算斐波那契数列:
- // 斐波那契数列:
- // 位置:1 2 3 4 5 6 7 8 ...................... 40
- // 数字:1 1 2 3 5 8 13 21 ...................... ?
-
- // 前两位数字,固定,是1
- // 后面的数字 = 前两位之和
-
- // 相信自己
- // 需求:计算一个位置的数字
- // 我的函数一定可以实现这个功能:当我需要计算一个位置的数字的时候,就调用函数
- function fn(n) {
- if (n <= 2) {
- return 1
- }
- // return n-1位置的数字 + n-2位置的数字
- return fn(n - 1) + fn(n - 2)
- }
-
- // console.log(fn(8))
- // console.log(fn(40)) // 102334155
-
- // -----------------------------------------------------------
- // 计算一个数的阶乘
- // 5的阶乘: 5 * 4 * 3 * 2 * 1
- // 4的阶乘: 4 * 3 * 2 * 1
- // 7的阶乘: 7 * 6 * 5 * 4 * 3 * 2 * 1
- // 8的阶乘: 8 * 7的阶乘
- // 40的阶乘:?
-
- // 我写一个函数,功能是 计算一个数的阶乘;当我需要计算一个数的阶乘的时候,就调用函数
- function fn(n) {
- if (n === 1) {
- return 1
- }
- // return n * n-1的阶乘
- return n * fn(n - 1)
- }
-
- console.log(fn(5)) // 120
- console.log(fn(7)) // 5040
异常处理,处理的是代码中一些未知的问题。
异常处理,也可以处理一些潜在的问题。比如函数有两个形参,但是调用的时候没有传递实参。
●throw 是关键字,用于抛出异常
●throw 一般和 new Error() 配合使用,用于抛出错误信息
●throw 后续的代码就不会执行了
- // 计算两个数的和
- // 那么,调用函数的时候,必须传递 两个数字进来
- function sum(a, b) {
- if (a === undefined || b === undefined) {
- // throw '必须传递两个实参'
- throw new Error('必须传递两个实参')
- }
- if (typeof a !== 'number' || typeof b !== 'number') {
- throw new Error('两个参数必须是数字类型')
- }
- console.log(a + b)
- }
-
- sum()
try...catch...finally...
- try {
- // 正常写你的功能代码
- // 这里放有可能有错误,也有没有没有错误的代码
- } catch (e) {
- // e 就是上面try里面抛出的错误信息
- } finally {
- // 无论上面的代码正常,还是有错误,finally里面的内容一定会执行的
- }
- try 不能单独使用,必须配合 catch 或 finally。或三者都有
- try 表示尝试;所以把我们写的代码,都放到 try 里面
- try 里面的代码如果正常执行了,没有问题,则不会进入catch;
- try 里面的代码出现了问题,则会进行catch,并且 e 就是错误信息
- // try...catch 防患于未然
- try {
- // 尝试执行xxxx代码
- console.log(123)
- fn() // 调用了一个不存在的函数
- console.log(456)
- } catch (e) {
- // console.log(e) // e 是一个错误对象;e.message 表示错误信息
- console.log(e.message)
- } finally {
- console.log('finally里面的代码一定会执行')
- }
在代码中,加一行 debugger ,代码运行到这里,就会暂停;适用于代码非常多的情况
- let a = 10
- debugger // 代码运行到这里,就会暂停;适用于代码非常多的情况
- a = 10 * 10
- a = a * 10
- a = a * 5
- console.log(a)
- 函数.call(this指向的对象, 其他参数1, 其他参数2, ...)
- 第1个参数,用于修改函数内this的指向。如果不希望修改函数内this的指向,填null
- 其他参数,都是传递给原函数的实参。
- // --------------------------------- call ----------------------------------
- let obj = { uname: '张三', age: 100 }
-
- function fn(a, b) {
- console.log(a + b)
- console.log(this)
- }
-
- // fn()
- fn.call(obj, 2, 5) // 将函数内的this,改为 obj 对象;并将2传递给a,将5传递给b
示例:用 Object.prototype.toString.call(变量) 检测数据类型
- console.log(Object.prototype.toString.call('hello')) // [object String]
- console.log(Object.prototype.toString.call([])) // [object Array]
- console.log(Object.prototype.toString.call(123)) // [object Number]
- console.log(Object.prototype.toString.call(null)) // [object Null]
- console.log(Object.prototype.toString.call({})) // [object Object]
- console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
- ........
- 函数.apply(this指向的对象, [其他参数1, 其他参数2, ...])
- 第1个参数,用于修改函数内this的指向。如果不希望修改函数内this的指向,填 null
- 其他参数,必须是一个数组,数组中的元素,都是传递给原函数的实参。
- 调用bind之后,并不会执行函数;而是返回一个新的函数。如果需要调用函数,需要加 () 调用
- // --------------------------------- apply ----------------------------------
- // let obj = { uname: '张三', age: 100 }
-
- function fn(a, b) {
- console.log(a + b)
- console.log(this)
- }
-
- fn.apply(obj, [2, 5]) // 将函数内的this,改为 obj 对象;并将2传递给a,将5传递给b
示例:
- document.querySelector('button').addEventListener('click', function () {
- console.log(this) // window
- }.bind(this)) // 传入bind 的this 是全局中的this,表示window
- this的取值 不取决于函数的定义,而是取决于怎么调用的(this指向调用者)
- 全局中的this:指向 window
- 直接调用: 比如 fn(),则函数内的this指向window
- 对象调用方法:比如 obj.abc() 则方法内的this指向调用对象obj
- 构造函数内和原型方法中:this指向实例对象
- 事件处理函数中:this指向事件源
- 特殊调用: 比如函数调用了 call、apply、bind,则this指向call、apply、bind的第一个参数
- 箭头函数:函数内没有绑定this的值,需按照作用域链向上层查找,指向上层作用域中的this。
防抖和节流,是两种性能优化方案。没有防抖和节流,程序也能够正常执行,应用了防抖或节流,程序的性能就会得到很大的提升。
防抖(debounce)
事件触发后,延迟1s执行
如果1s内,没有重复触发事件,则1s后执行事件代码
如果1s内,重复触发事件,则从此刻重新计时,仍然延迟1s执行
工作中,可以使用 lodash 封装的 _.debounce() 来实现防抖。
防抖的原理:
-
- <input type="text">
- <script>
- let ipt = document.querySelector('input')
- let timer = null
- ipt.addEventListener('input', function () {
- clearTimeout(timer)
- timer = setTimeout(() => {
- // 当用户输入完成之后,输出输入框的值
- console.log(this.value)
- }, 500)
- })
- </script>
库实现方法:
- 元素.addEventListener('事件类型', _.debounce(事件处理函数, 延迟时间))
-
- // ----------------------- 工作中使用 _.debounce -----------------------
- let ipt = document.querySelector('input')
- ipt.addEventListener('input', _.debounce(function () {
- // 当用户输入完成之后,输出输入框的值
- console.log(this.value)
- }, 500))
节流(throttle)
单位时间内(比如1s内),频繁触发事件,控制只有第1次有效,其他无效。
节流降低了事件的执行频率,稀释了事件。
节流的原理:
-
- <button>删除</button>
- <script>
- // ------------------- 自己实现节流 --------------------------
- // 控制用户,1s内,只有一次点击有效
- let prev = Date.now() // 表示上一次点击的时间
- document.querySelector('button').addEventListener('click', function () {
- let current = Date.now() // 点击时的当前时间
- if (current - prev >= 1000) {
- console.log('删除成功')
- prev = Date.now() // 记录一下当前时间,为下一次点击做准备
- }
- })
- </script>
使用 lodash 提供的 _.throttle() 方法实现节流:
- 元素.addEventListener('事件类型', _.throttle(事件处理函数, 间隔时间))
-
- // ------------------- 工作中,使用 _.throttle() -------------
- document.querySelector('button').addEventListener('click', _.throttle(function () {
- console.log('删除成功')
- }, 1000))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。