面向对象😺
-
面向对象本质是 对面向过程的封装
面向过程 : 注重的是过程
面向对象 : 注重的是结果
面向对象三大特征
- 继承:一个对象(子对象)拥有另一个对象(父对象)的所有成员(js主要用继承)
- 封装:把代码放入对象的方法中
- 多态:一个对象 在不同情况下的不同状态(js语言基本不涉及多态)
内置对象😺
数组对象api
let arr=[10,20,30,40,50] // new Array(10,20,30,40,50)
- arr.concat(数组)
连接数组,返回值是连接后的数组
应用:上拉加载下一页。 需要将下一页的数组连接到后面
arr = arr.concat([5, 6, 7])
console.log(arr) //[10, 20, 30, 40, 50, 5, 6, 7]
-
arr.join(‘分隔符’)
把数组中的每一个元素连接成字符串(括号里面什么都不放,默认分隔符是逗号 放个空字符串'',就是紧密连接)
应用:把数组元素拼接成字符串在页面显示。例如:歌手['周杰伦','蔡依林'] -->周杰伦/蔡依林
let str = arr.join('|') console.log(str) //10|20|30|40|50|5|6|7 -
arr.reverse()
翻转数组
arr.reverse() console.log(arr) //[7, 6, 5, 50, 40, 30, 20, 10] -
arr.sort()
数组排序
let numArr=[20,55,30,10,90] numArr.sort(function(a,b){ // return a - b //从小到大 //[10, 20, 30, 55, 90] return b - a //从大到小 //[90, 55, 30, 20, 10] }) console.log(numArr)
字符串对象api
let str = '祝大家女神节快乐!'
-
字符串类似于数组,也有长度和下标
console.log(str.length) //8 console.log(str[5]) //节 -
str.indexOf(‘字符串’)
获取‘字符串’首字母在str中的下标(如果存在,返回首字母下标;如果不存在,则返回固定值-1)
应用:可以判断一个字符串 在不在str中
console.log(str.indexOf('女神')) //3
console.log(str.indexOf('女快')) //-1
- str.split(‘分隔符’)
以分隔符分割str,分割的每一部分放入数组中
应用:解析url中的参数
let url = 'http://www.baidu.com?name=张三&age=20'
console.log(url.split('?')) //['http://www.baidu.com', 'name=张三&age=20']
console.log(url.split('=')) //['http://www.baidu.com?name', '张三&age', '20']
//取到'张三'
console.log(url.split('?')[1].split('&')[0].split('=')[1]) //张三
- str.substr(下标,长度)
从‘下标’开始截取‘长度’的字符串
console.log(str.substr(3, 3)) //女神节 从3下标开始截取3个字
- 大小写转换(中文没有大小写)
console.log('aedJNMIsswSEW'.toLocaleUpperCase()) //AEDJNMISSWSEW
console.log('aedJNMIsswSEW'.toLocaleLowerCase()) //aedjnmisswsew
原型对象😺
工厂函数
函数作用是用于 创建对象 的函数
构造函数
构造函数作用与工厂函数一致。但是代码更加简洁
- 构造函数new工作原理:
- 创建空对象
- this指向这个对象
- 对象赋值
- 返回这个对象
构造函数new在使用时需注意的地方:
-
构造函数首字母一般大写,为了提醒调用者不要忘记关键字new
-
如果构造函数内部 手动return:
(1)return 值类型:无效,还是返回new创建的对象
(2)return引用类型(数组、函数、对象):有效,会覆盖new创建的对象
function Person(name, age, sex) {
//(1)创建空对象 {}
//(2)this指向这个对象 this = {}
//(3)对象赋值
this.name = name
this.age = age
this.sex = sex
//(4)返回这个对象 return this
}
let p1 = new Person('张三', 20, '男')
let p2 = new Person('李四', 22, '女')
console.log(p1, p2)
原型对象
任何函数在声明的时候,系统会自动帮你创建一个对象,称之为原型对象
作用:解决 内存浪费 和 变量污染
原型对象三个相关属性: 描述构造函数、原型对象、实例对象三者关系
- prototype : 属于构造函数, 指向原型对象
作用:解决构造函数内存浪费 + 变量污染
- constructor : 属于原型对象,指向构造函数
作用:可以让实例对象知道自己是被哪个构造函数创建的
proto : 属于实例对象,指向原型对象
作用:让实例对象直接访问原型成员
//1.构造函数
function Person(name, age) {
this.name = name
this.age = age
}
//2.原型对象
Person.prototype.eat = function () {
console.log('吃东西')
}
Person.prototype.learn = function () {
console.log('学习')
}
console.log(Person.prototype.constructor) //Person
console.log(Person.prototype.constructor === Person) //true
//3.实例对象
let p1 = new Person('张三', 20)
console.log(p1)
/*
为什么实例对象可以直接访问原型中的成员,本质是通过__proto__来访问的
__proto__ : 属于实例对象的,指向原型对象
注意: 这个属性不是web标准,很多浏览器不会显示的。在实际开发中必须要省略
*/
p1.eat() //p1.__proto__.eat()
/* 验证 构造函数、原型对象、实例对象三者关系 */
console.log(p1.__proto__.constructor) //Person
console.log(Person.prototype === p1.__proto__) //true
-
静态成员与实例成员:
静态成员: 属于函数的成员
实例成员:属于实例对象的成员
function Person(name, age) { this.name = name this.age = age } let p1 = new Person('张三', 20) console.log(p1.name) //实例成员 console.log(p1.age) //实例成员 console.log(Person.prototype) //静态成员 console.log(Math.PI) //静态成员 -
Object的value方法
Object.values( 对象名 )
/* 需求:获取对象所有的属性值 : Object.values( 对象名 )*/
let person = {
name: '张三',
age: 20,
sex: '男'
}
//1 以前的写法: for-in 循环
for (let key in person) {
console.log(person[key])
}
//2.静态方法 Object.values(对象名)
//返回值是一个数组,会存储对象每一个属性值
console.log(Object.keys(person)) //['name', 'age', 'sex']
console.log(Object.values(person)) //['张三', 20, '男']
原型继承(important!)😺
-
原型继承:把父对象(实例)作为子对象 构造函数的原型
PicModal继承modal的open方法:
原型继承: 把父对象(实例) 作为子对象构造函数的原型
(1)若Modal.prototype 有自己的eat
(2)如果把父对象原型 作为 子对象原型. 只要给子对象原型添加eat就会覆盖Modal原来的eat
(3)如果把父对象实例 作为 子对象原型. 给子对象原型添加eat,本质是给父对象实例添加,不会污染Modal原来的eat
/*
1.继承 : 一个对象 拥有 另一个对象 所有的 成员
2.原型继承 :把父对象 作为子对象构造函数的原型
*/
//父对象
let father = {
house: {
address: '深圳湾一号',
price: 20000000
},
car: {
brand: '劳斯莱斯幻影',
price: 15000000
}
}
//子对象
//构造函数
function Son(name, age) {
this.name = name
this.age = age
}
//原型继承: 把父对象(实例) 作为子对象 构造函数的原型
Son.prototype = father
//可选 : 原型继承之后,由于父对象覆盖原来的 子对象构造函数原型, 就会导致constructor消失.
//解决办法: 手动添加。(对开发几乎没有影响,也可以不加)
Son.prototype.constructor = Son
//实例对象
let s1 = new Son('ikun', 30)
let s2 = new Son('班长', 20)
console.log(s1, s2)
原型链😺
原型链
-
原型链::每一个实例对象都有自己的原型,而原型也是对象,也有自己的原型,以此类推形成链式结构. 称之为原型链
-
对象访问原型链规则:就近原则
对象优先访问自己的属性,自己没有才会访问原型,原型也没有就访问原型的原型,以此类推直到原型链终点(null),如果还没有,属性则获取undefined,方法则报错:xxx is not a function
-
原型链作用(!):继承
//1.构造函数
function Person(name, age) {
this.name = name
this.age = age
// this.type = '学生'//如果自己有type,优先访问自己的(就近原则)
}
//2.原型对象 : 存储具有共同特征的数据
Person.prototype.type = '哺乳动物'
Person.prototype.country = '中国'
Person.prototype.eat = function () {
console.log(this.name + '吃东西')
}
//3.实例对象
let p1 = new Person('帅哥', 20)
let p2 = new Person('美女', 20)
console.log(p1)
/* 小测试 */
console.log(p1.name) //帅哥 p1自己有name属性
console.log(p1.age) //20 p1自己有age
console.log(p1.type) //哺乳动物 p1自己没有type,但是p1的原型有
console.log(p1.girlFrined) //undefined p1自己没有girlFrined, p1的原型也没有 girlFrined
p1.eat() // 吃东西
// p1.learn()//报错 undefined is not a function
/* 思考: p1自己没有toString, p1的原型也没有toString, 但是为什么不报错呢?
原因: p1的原型的原型有toString
*/
p1.toString()
/* 如何查看实例对象原型 : 两行 */
//查看p1的原型
console.log(p1.__proto__.constructor) //Person
console.log(Person.prototype === p1.__proto__) //true
//查看p1的原型的原型
console.log(p1.__proto__.__proto__.constructor) //Object
console.log(Object.prototype === p1.__proto__.__proto__) //true
//查看p1的原型的原型的原型
console.log(p1.__proto__.__proto__.__proto__) //null

内置对象原型链
// 数组对象
//实例化对象
let arr = [10,20,30]//new Array(10,20,30)
console.log( arr )
//1.1 查看arr的原型
console.log( arr.__proto__.constructor )//Array
console.log( arr.__proto__ === Array.prototype )//true
//1.2 查看arr的原型的原型
console.log( arr.__proto__.__proto__.constructor )//Object
console.log( arr.__proto__.__proto__ === Object.prototype )//true
// 字符串对象
let str = new String('abc')
console.log( str )
//2.1 查看str的原型
console.log( str.__proto__.constructor )//String
console.log( str.__proto__ === String.prototype )//true
//2.2 查看arr的原型的原型
console.log( str.__proto__.__proto__.constructor )//Object
console.log( str.__proto__.__proto__ === Object.prototype )//true
// 日期对象
let date = new Date()
/* js有几个特殊的对象 无法使用 log来打印的,需要用dir来打印: function date dom对象 */
console.dir( date )
//3.1 查看date的原型
console.log( date.__proto__.constructor )//Date
console.log( date.__proto__ === Date.prototype )//true
//3.2 查看date的原型的原型
console.log( date.__proto__.__proto__.constructor )//Object
console.log( date.__proto__.__proto__ === Object.prototype )//true
DOM对象原型链
<body>
<div>我是div</div>
<p>我是pp</p>
<a href="#">我是aa</a>
<script>
let div = document.querySelector('div')
let p = document.querySelector('p')
let a = document.querySelector('a')
</script>
</body>

instanceof运算符
-
instanceof(关键字):运算符。检测某个构造函数的prototype在不在实例对象的原型链中
说人话:亲子鉴定,鉴定两个对象间有没有血缘关系
语法:实例对象 instanceof 构造函数
检测 右边的构造函数的prototype 在不在 左边实例对象的原型链中
true:在 false:不在
-
重点:instanceof运算符原理. 检测右边构造函数prototype是否在左边实例对象得原型链中
let arr = [10, 20, 30]
let str = new String('abc')
// arr-> Array.prototype -> Object.prototype -> null
console.log(arr instanceof Array) //true
console.log(arr instanceof Object) //true
console.log(arr instanceof String) //false
// str-> String.prototype -> Object.prototype -> null
console.log(str instanceof Array) //false
console.log(str instanceof Object) //true
console.log(str instanceof String) //true
//封装一个函数,要求这个函数必须要传数组类型、 传其他类型不可以
function fn(arr) {
if (arr instanceof Array) {
console.log(arr.reverse())
} else {
console.log('数据类型错误')
}
}
fn([10, 20, 30])
fn('abc')
ES6😺
剩余参数
- 剩余参数(rest参数):获取函数剩余的所有参数
- 语法:function 函数名(形参1,形参2,...形参3){ }
- 特点:
- 只能作为最后一个参数
- 是真数组
- arguments关键字:获取函数所有实参
- 是一个伪数组,有数组三要素(元素、下标、长度),但是不能使用数组的方法
- 应用:一般用户参数数量不限的函数
- 例如:arr.push() 、Math.max() 这些函数实参数量不限,底侧原理就是使用arguments来接受所有的实参
一般情况下,剩余参数可以取代arguments
函数默认参数
- 语法:function 函数名(形参=默认值){ }
function fn(a = 10, b = 20) {
//以前: 逻辑或短路
// 找真 : 左边是真就返回左边式子的值,否则返回右边式子的值
// a = a || 10
// b = b || 20
console.log(a + b)
}
fn(5)
let与const
-
ES5声明变量:var
- 有变量预解析:声明提前
- 没有块级作用域:分支和循环大括号内都是全局
-
ES6声明变量:let、const
- 没有变量预解析:变量先声明后赋值
- 有块级作用域 : 分支和循环大括号内是局部(没有变量污染)
-
let和const的区别:
let:变量。可以修改
const:常量。不可以修改,内存更安全,效率更高
对象解构赋值
-
解构赋值:变量赋值的简写(解构精髓:当变量名和对象属性值一致时,只需要写一个)
-
把对象的属性值赋值给变量
let { name,age,sex} = obj
-
把变量的值赋值给对象
let obj = {name,age,sex}
-
let obj = {
name: '张三',
age: 20,
sex: '男'
}
//1. 取出对象的属性 赋值 给 变量
//ES5
// let name = obj.name
// let age = obj.age
// let sex = obj.sex
// let score = obj.score
//ES6
let {
name,
age,
sex,
score
} = obj
console.log(name, age, sex, score) //'张三',20,'男',undefined
//2. 取出变量的属性 赋值 给 对象
let username = 'admin'
let password = '123456'
let a = 'aaa123'
//ES5
// let user = {
// username:username,
// password:password
// }
//ES6
let user = {
username,
password: a,
eat() { //等价于 eat:function(){}
console.log(111)
}
}
console.log(user)
user.eat()
数组解构赋值
- 数组解构:
- 取出数组元素 赋值给变量
- 取出变量的值 赋值给数组元素
let arr = [10, 20, 30]
//ES5
// let n1 = arr[0]
// let n2 = arr[1]
// let n3 = arr[2]
// let n4 = arr[3]
//ES6
let [n1, n2, n3, n4] = arr
console.log(n1, n2, n3, n4) //10 20 30 undefined
//取出变量的值 赋值给数组
let num1 = 1
let num2 = 2
let arr1 = [num1, num2]
console.log(arr1) //1,2
函数解构赋值
function fn({ name, age }) { //let {name,age}={ name: '李四', age: 25 }
//把参数解构
// let {name,age}=obj
// let name = obj.name
// let age = obj.age
console.log(name, age)
}
fn({ name: '李四', age: 25 })
箭头函数
- 箭头函数:相当于function函数的简写
- 把function改成箭头 =>
- 形参小括号()写到箭头左边
- 箭头函数语法注意点:
- 如果箭头函数只有一个形参,则可以省略小括号
- 如果箭头函数的 函数体 只有一行代码,则可以省略大括号。 (此时必须省略return)
let fn = (a, b) => {
console.log(a + b)
}
fn(10, 20)
//(1)如果箭头函数只有一个形参,则可以省略小括号
let fn1 = a => { return a * a }
console.log(fn1(10))//100
//(2)如果箭头函数的 函数体 只有一行代码,则可以省略大括号。 (此时必须省略return)
let fn2 = a => a * 2
console.log(fn2(9))//18
箭头函数的this指向
箭头函数本质是访问 上级作用域中的this
- 箭头函数不能上下文调用 : 无法修改this指向
- 箭头函数不能作为构造函数
- 有两种函数最好不要是箭头函数:构造函数、事件处理函数
let fn = () => {
//1级
console.log(this)
}
fn()
let obj = {
name: '张三',
eat: fn
}
obj.eat()
//由于箭头函数没有this,所以箭头函数不能作为构造函数 (new会修改this指向,而箭头没有this)
// new fn()
//箭头函数也无法修改this (call apply bind)对箭头函数是无效的
fn.call({ name: '张三' })
//由于箭头函数没有this,所以箭头函数一般不作为事件处理函数
-
箭头函数this指向景点面试题:
let obj = { name: "ikun", eat() { //1级 this : obj function fn1() { //2级 console.log(this)//window } fn1() let fn2 = () => { //2级 : 箭头函数访问上级 1级obj console.log(this)//obj } fn2() }, learn: () => { //1级 : 上级 this->window function fn1() { console.log(this)//window } fn1() let fn2 = () => { //2级 访问1级 this -> window console.log(this)//window } fn2() } } obj.eat() obj.learn()
展开运算符
-
展开运算符:...
相当于对象遍历的简写
-
应用:
- 连接两个数组/对象
- 求数组最大值
let arr1 = [10, 20, 30]
let arr2 = [40, 50, 60]
//ES5 : concat()
// let arr = arr1.concat(arr2)
// arr.push(70)
//ES6
//连接数组
let arr = [...arr1, ...arr2, 70]
console.log(arr) //[10, 20, 30, 40, 50, 60, 70]
//连接对象
let student = {
score: 90,
car: {
name: '张三'
}
}
let teacher = {
name: 'kunkun',
...student
}
console.log(teacher) //{name: 'kunkun', score: 90, car: {…}}
//求数组最大值
let max = Math.max(...arr1, ...arr2, 100)
console.log(max) //100
数据类型Set
以下所有的方法,都可以使用传统的for循环来代替,只是语法不同而已
| 数组几种遍历介绍(共同点:回调函数一样) | 应用场景 | 回调执行次数 | 函数返回值 | 回调是否需要return |
|---|---|---|---|---|
| map遍历 | 映射数组 | == 原数组长度 | 新数组(==) | 一定要return(当前元素) |
filter遍历 | 过滤数组 | == 原数组长度 | 新数组(!=) | return true(元素添加到新数组) |
| forEach遍历 | 遍历数组 | == 原数组长度 | 无 | 无 |
| some遍历 | 找出符合条件的数 | != 原数组长度 | 布尔类型 | return true;循环结束 |
| every遍历 | 判断所有元素是否符合条件 | != 原数组长度 | 布尔类型 | return true; 循环继续 |
| findIndex遍历 | 获取符合条件的第一个元素位置(下标) | != 原数组长度 | 数字 | return true; 循环结束 |
| includes方法(底层是遍历) | 判断数组/字符串是否包含某一个值 | 无回调 | 布尔类型 | 无回调 |
-
数据类型Set:集合(类似于数组)
Set相当于是数组类型,Set和数组最大区别是:Set无法存储重复数据
-
应用:数组去重
let newArr = [ ...new Set(需要去重的数组) ]
let arr = [10, 20, 30, 50, 60, 20, 50, 60]
console.log(arr)
//1.创建Set,去除数组重复元素
let set = new Set(arr)
console.log(set) //{10, 20, 30, 50, 60}
// 2.把set变成真数组
let newArr = [...set]
//经典面试题: 一行代码实现数组去重
// let newArr = [...new Set(arr)]
console.log(newArr) //[10, 20, 30, 50, 60]
数组map方法
-
map作用与场景:映射数组(将数组每一个元素处理后,得到一个新数组)
举例子: 商品打八折(数组中每一个元素都要乘以0.8)
-
经典场景:数据驱动渲染dom树(将数组直接渲染到页面)
-
语法特点:
-
循环执行次数 == 数组长度
-
回调函数内部return作用:
(1)return 新元素值
(2)没有return,新元素都是undefined
-
map本身返回值作用:映射之后的新数组
-
let arr = [20, 66, 80, 90, 100]
//完整写法
// let res = arr.map((item, index) => {
// return item * 0.8
// })
//简写
let res = arr.map(item => item * 0.8)
console.log(res) //[16, 52.800000000000004, 64, 72, 80]
-
map方法渲染到页面:
封装渲染函数,使用map来渲染数据;由于页面会渲染很多次,而且每一次渲染的数组也不一样。
数组不一样解决方案:给渲染函数添加一个‘函数’
const renderData = arr => { document.querySelector('.list').innerHTML=arr.map(item=>``).join('') }
数组filter方法
-
filter作用与场景:筛选数组
-
经典场景:筛选,根据条件筛选数组,将符合条件的元素放入新数组中
-
语法特点:
-
循环执行次数 == 数组长度
-
回调函数内部return作用:
(1)return true:满足筛选条件,当前元素放入新数组中
(2)return false:不满足筛选条件,当前元素不放入新数组中
-
filter本身返回值作用:筛选之后的新数组
-
let arr = [20, 61, 80, 95, 100]
//需求: 筛选出数组中的偶数
//完整写法
// let res = arr.filter((item, index) => {
// console.log(item, index)
// if (item % 2 == 0) {
// return true
// }
// })
//简介写法
let res = arr.filter(item => item % 2 == 0)
console.log(res) //[20, 80, 100]
数组forEach
- 数组forEach方法作用:遍历数组
- 应用场景和 for(let i=0;i<arr.length;i++){} 功能一致
- 特点:
- 回调函数执行次数 == 数组长度
- 回调函数内部的return:没有返回值 undefined
- forEach方法的返回值:没有返回值
let arr = [45, 60, 88, 90, 20]
arr.forEach((item, index) => {
console.log(item, index)
})
数组some方法
-
some作用与场景:
判断数组中是否有满足条件的元素(逻辑或||) -
经典场景:非空判断. 如:多个表单只要有一个是空的,就不能提交
-
语法特点:
-
循环执行次数 != 数组长度
-
回调函数内部return作用:
(1)return true:循环结束. 找到满足条件的元素,此时some的返回值也是true
(2)return false:循环继续. 没有找到满足条件的元素,如果循环执行完毕还是 false,最终some的返回值也是false
-
some本身返回值作用
return true : 有满足条件的元素
return false : 没有满足条件的元素
-
//需求: 判断数组中有没有负数
let arr = [10, 20, 50, 60, 70, 80]
//标准写法
// let res = arr.some((item, index) => {
// if (item < 0) {
// return true
// }
// })
//简写
let res = arr.some(item => item < 0)
console.log(res) //false (所以没有满足条件的元素)
数组every方法
-
every作用与场景:
判断数组中是否所有元素都满足条件(逻辑与&&) -
经典场景:开关思想. 购物车全选
-
语法特点:
-
循环执行次数 != 数组长度
-
回调函数内部return作用:
(1)return true:循环继续. 当前元素满足条件,继续判断. 如果循环执行完毕还是true,则every的返回值就是true
(2)return false:循环结束. 当前元素不满足条件,every的返回值也是false
-
every本身返回值作用
return true : 全部元素都满足条件
return false : 有元素不满足条件
-
//需求: 判断数组中是否所有数都是正数
let arr = [10, 20, 50, 60, 70, 80]
//标准写法
// let res = arr.every((item, index) => {
// if (item > 0) {
// return true
// }
// })
//简写
let res = arr.every(item => item > 0)
console.log(res) //true (所有数都是正数)
数组findIndex方法
-
数组findIndex方法作用:查找元素下标
-
数组的findIndex与indexOf异同点:
相同点:功能一致,都是查找元素下标。 有则返回下标,无则返回固定值-1
不同点:应用场景不同
- indexOf : 查找数组中的元素都是值类型
- findIndex : 查找数组中的元素都是引用类型
-
findIndex方法特点:
-
回调函数执行次数 != 数组长度
-
回调函数内部的return
return true:循环结束。 找到了,此时返回值就是下标
return false:循环继续。 没找到,循环继续。 如果所有元素全部遍历还是没找到,最终结果就是-1
-
-
findIndex本身返回值作用
return -1 : 没有
return 下标 : 有
-
findIndex方法返回的是索引值(下标)
-
find方法返回的是具体属性值
-
find()方法返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined
let arr = [
{ name: '张三', age: 20 },
{ name: '李四', age: 18 },
{ name: '王五', age: 16 },
]
//需求:找名字为王五的人在哪里
// let res = arr.findIndex(item=>{
// if( item.name == 'sfs' ){
// return true
// }else{
// return false
// }
// })
let res = arr.findIndex(item => item.name == '王五')
console.log(res)//2
数组reduce方法
-
数组reduce方法:数组累加器方法
对数组每一个元素执行一次回调函数,累加最后一次回调的结果
-
reduce场景:数组元素求和、求数组元素最大值
let arr = [20,55,60]
// let sum = 0
// for(let i = 0;i<arr.length;i++){
// sum = sum + arr[i]
// }
// console.log( sum )
/*
第一个参数:回调 (上一次值,当前值,当前下标)=>{}
* 默认下标不是从0开始,而是从1开始。 开发中一般需要设置默认值
* return 值 就是下一次 sum的值
第二个参数: 初始值
* 一般需要设置初始值为0, 如果不设置遇到空数组则会报错
reduce方法返回值是 : 最后一次sum的结果
*/
// let res = arr.reduce( ( sum,item,index )=>{
// console.log( sum,item,index)
// return sum + item
// } , 0 )
let res = arr.reduce( ( sum,item )=>sum + item , 0 )
console.log( res )
数组includes方法
**包含: includes( )**方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
函数this的三种指向😺
-
环境对象 this:谁‘调用’我,我就指向谁
-
普通函数:函数名() this -> window
-
构造函数:new 函数名() this -> new创建的实例对象
-
对象方法:对象名.方法名() this -> 对象
小技巧:没点没new是window,有new是实例,有点就是点左边的对象
-
//作用域链
let obj = {
name: "张三",
eat: function () {
//1级链
console.log(this) //1.obj
function fn() {
//2级链
console.log(this) //2.window
}
fn()
}
}
let eat = obj.eat
obj.eat()
函数上下文调用😺
- 默认情况下,函数内部的this是固定的,无法动态修改,如果需要修改,则需要使用函数上下文调用(函数上下文:函数作用域)
call()调用函数
函数名.call(修改后的this,参数1,参数2…………)
let fn = function (a, b) {
console.log(this)
console.log(a + b)
}
// 函数名.call(修改后的this,参数1,参数2…………)
fn.call({
name: '齐齐'
}, 20, 30)
call()场景1:Array.from(伪数组)伪数组转真数组
-
伪数组:有数组三要数(下标、长度、元素),但没有数组的方法(本质是对象)
Array.from(伪数组)
let weiArr = {
0:88,
1:20,
2:50,
3:60,
length:4
}
console.log( weiArr )
//如果希望伪数组也可以调用数组的方法(排序、拼接),就需要把伪数组转成真数组
/*
1. slice可以查询数组,默认情况下不传参这个方法会得到数组本身
2. 但是伪数组由于原型不是Array,所以无法调用slice
3. slice方法存储在哪里? : Array.prototype
*/
weiArr = Array.prototype.slice.call(weiArr)
console.log(weiArr)
// weiArr.slice()
//实际开发中,ES6新增语法用于伪数组转真数组 : Array.from(伪数组)
let arr = Array.from(weiArr)
console.log( arr )
call()场景2:万能数据类型检测
-
typeof 数据:有两种数据类型无法检测(null和数组:结果都是‘object’)
-
解决方案:万能数据类型检测
Object.prototype.toString.call(数据)
//值类型
let str = 'abc'
let num = 123
let bol = true
let und = undefined
let nul = null
//引用类型
let arr = [10, 20, 30]
let fn = function () {}
let obj = {
name: '张三'
}
console.log(typeof str) //'string'
console.log(typeof num) //'number'
console.log(typeof bol) //'boolean'
console.log(typeof und) //'undefined'
console.log(typeof nul) //'object'
console.log(typeof arr) //'object'``
console.log(typeof fn) //'function'
console.log(typeof obj) //'object'
/* 万能数据类型检测: Object.prototype.toString.call(数据)
原因: 在Object.prototype中有一个方法叫toString,返回一个固定格式字符串 '[object 数据类型]'
*/
console.log(Object.prototype.toString.call(str)) //'[object String]'
console.log(Object.prototype.toString.call(num)) //'[object Number]'
console.log(Object.prototype.toString.call(bol)) //'[object Boolean]'
console.log(Object.prototype.toString.call(und)) //'[object Undefined]'
console.log(Object.prototype.toString.call(nul)) //'[object Null]'
console.log(Object.prototype.toString.call(arr)) //'[object Array]'
console.log(Object.prototype.toString.call(fn)) //'[object Function]'
console.log(Object.prototype.toString.call(obj)) //'[object Object]'
apply()调用函数
函数名.apply(修改后的this,数组/伪数组)
function fn(a, b) {
console.log(a + b)
console.log(this)
}
// (1)函数名.call(修改后的this,参数1,参数2…………)
fn.call({
name: '张三'
}, 10, 20)
// (2)函数名.apply(修改后的this, 数组/伪数组 )
// apply底层会自动帮你遍历数组,然后按照顺序逐一传参
fn.apply({
name: '李四'
}, [30, 40])
apply()场景1:伪数组转真数组
let weiArr = {
0: 20,
1: 66,
2: 88,
3: 90,
length: 4
}
//需求: 伪数组转真数组
let arr = []
//思路一: 把伪数组每一个元素push到数组中
// arr.push( weiArr[0],weiArr[1],weiArr[2],weiArr[3])
// for (let i = 0; i < weiArr.length; i++) {
// arr.push(weiArr[i])
// }
//思路二: 使用apply arr.push.apply(arr,伪数组)
//这里不需要修改this,只是借助apply传参的特点. this指向原来是谁,还是设置谁
arr.push.apply(arr, weiArr)
console.log(arr)
//思路三: 伪数组转真数组 : ES6提供一个更简洁的静态方法
// Array.from( 伪数组 )
console.log(Array.from(weiArr))
apply()场景2:求数组最大值
let max = Math.max(...arr)
//求数组最大值
let arr = [20, 50, 66, 100, 30]
//思路一: .js基础 : 擂台思想
let max = arr[0]
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i]
}
}
console.log(max)
//思路二: Math.max()
// let max1 = Math.max(arr[0],arr[1],arr[2],arr[3],arr[4])
//这里使用apply只是借助传参特点,this指向不用修改。还是原来的this
//Math.max.apply(Math,数组/伪数组)
let max1 = Math.max.apply(Math, arr)
console.log(max1)
//思路三:
/* ES6有更简单的方式可以求数组最大值
... 展开运算符,自动遍历数组传参(类似于apply传参特点)
*/
let max2 = Math.max(...arr)
console.log(max2)
bind()调用函数
函数名.bind(修改后的this)
function fn(a, b) {
console.log(a + b)
console.log(this)
}
// 函数名.call(修改后的this,形参1,形参2…………)
fn.call({
name: '张三'
}, 10, 20)
// 函数名.apply(修改后的this, 数组或伪数组 )
// apply会自动帮你遍历数组,然后按照顺序逐一传参
fn.apply({
name: '李四'
}, [30, 40])
//函数名.bind(修改后的this)
// bind不会立即执行函数,而是返回一个修改this之后的新函数
let newFn = fn.bind({
name: '王五'
})
newFn(100, 200) //300
newFn(10, 20) //30
bind()场景:修改定时器的this
定时器的this:默认指向window
setTimeout(function () {
console.log(this) //{name: '李四'}
}.bind({
name: '李四'
}), 3000)
// let obj = {
// name: "张三",
// hobby: ["学习", "干饭", "睡觉"],
// sayHi: function() {
// setTimeout(function() {
// console.log(this)
// }, 1000)
// }
// }
// obj.sayHi()
经典面试题:call、apply、bind的异同点
- 相同点:作用一致,都是修改函数this的指向
- 不同点:
- 传参方式不同 : call是单个传参, apply是数组/伪数组传参
- 执行机制不同 : call和apply会立即执行函数,而bind不会立即执行而是返回一个修改this之后的新函数
闭包😺
-
闭包closure:
- 闭包是一个 访问其他函数内部变量 的 函数
- 闭包 = 函数 + 上下文引用
-
闭包作用:解决变量污染
//局部作用域 : 在函数内部声明
function fn() {
//局部变量num
let num = 10
// test函数 + 引用num 这个组合才叫闭包
function test() {
console.log(num)
}
test()
}
fn()
-
example:
<body> <input type="text" placeholder="请输入搜索内容"> <button class="btn">点我搜索</button> <script> document.querySelector('.btn').onclick = function () { //1.获取用户搜索的内容 let txt = document.querySelector('input').value //2.网络请求 : 不是立即就能出结果的,网络请求需要时间的。 //使用定时器来模拟请求 //定时器使用了input setTimeout(function () { alert(`${txt}的搜索结果如下:123条`) }, 1000) } </script> </body>
递归😺
-
递归函数:一个函数 在内部 调用自己
递归作用和循环类似,也需要有结束条件(优先用循环,递归少用)
function fn() { console.log('今天学得很开心') fn() } // fn() //双函数递归 : 两个函数互相调用 function fn1() { console.log('哈哈') fn2() } function fn2() { console.log('呵呵') fn1() } // fn1() -
递归应用:
-
浅拷与深拷贝
浅拷贝:拷贝地址,修改拷贝后的数据对原数据有影响
深拷贝:拷贝数据,修改拷贝后的数据对原数据没有影响
-
方式一:JSON方式实现(推荐)
let newObj = JSON.parse( JSON.stringify( obj ))
-
方式二:递归(了解)
-
-
递归遍历dom树
-
浅拷贝与深拷贝(JSON实现)
let obj = {
name: '张三',
age: 20,
sex: '男',
hobby: ['吃饭', '睡觉', '学习']
}
//1.浅拷贝 : 拷贝地址
// let newObj = obj
// newObj.name = '李四'
// newObj.hobby[0] = '游戏'
// console.log(obj, newObj) //修改newObj,obj也会跟着改
//2.深拷贝 : 使用JSON
//(1)把obj转成json格式字符串 : 底层会自动深拷贝
// let jsonStr = JSON.stringify(obj)
//(2)把json转成对象
// let newObj = JSON.parse(jsonStr)
let newObj = JSON.parse(JSON.stringify(obj))
newObj.name = '李四'
newObj.hobby[0] = '游戏'
console.log(obj, newObj)
浅拷贝与深拷贝(递归实现)
let obj = {
name: '张三',
age: 20,
sex: '男',
hobby: ['吃饭', '睡觉', '学习'],
student: {
name: "班长",
score: 90
}
}
//使用递归函数
function copy(obj, newObj) {
for (let key in obj) {
if (obj[key] instanceof Array) {
//声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = []
//递归调用继续拷贝 数组
copy(obj[key], newObj[key])
} else if (obj[key] instanceof Object) {
//声明一个空对象
newObj[key] = {}
//递归调用继续拷贝 对象
copy(obj[key], newObj[key])
} else {
newObj[key] = obj[key]
}
}
}
//创建一个空对象,然后开始深拷贝
let newObj = {}
copy(obj, newObj)
newObj.name = '李四'
newObj.hobby[0] = '摸鱼'
newObj.student.name = '坤坤'
console.log(obj, newObj)
递归遍历dom树
<style>
* {
padding: 0;
margin: 0;
}
.menu p {
width: 100px;
border: 3px solid;
margin: 5px;
}
.menu>div p {
margin-left: 10px;
border-color: red;
}
.menu>div>div p {
margin-left: 20px;
border-color: green;
}
.menu>div>div>div p {
margin-left: 30px;
border-color: yellow;
}
</style>
</head>
<body>
<div class="menu">
<!-- <div>
<p>第一级菜单</p>
<div>
<p>第二级菜单</p>
<div>
<p>第三级菜单</p>
</div>
</div>
</div> -->
</div>
<script>
//服务器返回一个不确定的数据结构,涉及到多重数组嵌套
let arr = [{
type: "电子产品",
data: [{
type: "手机",
data: ["iPhone手机", "小米手机", "华为手机"]
},
{
type: "平板",
data: ["iPad", "平板小米", "平板华为"]
},
{
type: "智能手表",
data: []
}
]
},
{
type: "生活家居",
data: [{
type: "沙发",
data: ["真皮沙发", "布沙发"]
},
{
type: "椅子",
data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
},
{
type: "桌子",
data: ["办公桌"]
}
]
},
{
type: "零食",
data: [{
type: "水果",
data: []
},
{
type: "咖啡",
data: ["雀巢咖啡"]
}
]
}
]
addElement(arr, document.querySelector('.menu'))
//封装一个遍历dom树函数
function addElement(arr, father) {
//遍历数组
for (let i = 0; i < arr.length; i++) {
//(1)创建空标签
let div = document.createElement("div")
//(2)设置内容
div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
//(3)添加到父盒子
father.appendChild(div)
//如果元素还有data属性,则需要使用递归继续添加下级菜单
if (arr[i].data) {
addElement(arr[i].data, div)
}
}
}
addElement(arr, document.querySelector(".menu"))
</script>
</body>