1.闭包以及应用场景:
闭包=内层函数+外层函数的变量
表现形式:函数之间相互嵌套
内层函数能够访问到外层函数的变量
作用:延长变量的生命周期,能够使函数外部访问到函数内部的变量(实现数据私有)
缺点:会使内存泄漏
1.1通过闭包的形式统计执行次数
functionfn(){
leti=0
functioninner(){
i++
console.log(`函数执行调用了${i}次`)
}
retureninner
}
constresult=fn()
result()//函数执行调用了1次
result()//函数执行调用了2次
result()//函数执行调用了3次
2.作用域链
1)作用域链本质是底层的变量查找机制
2)函数执行是优先在当前作用域查找变量,如果没有会逐层向上级作用域查找,直到全局作用域
3.垃圾回收机制
垃圾回收机制简称GC
1)在JS中内存的分配和回收都是自动完成的,内存在不使用时会被垃圾回收器自动回收
2)不了解垃圾回收机制很容易造成内存泄漏(就是用不到的内存,没有及时释放会造成内存泄漏)
3.1内存的生命周期
1)内存分配:声明变量、函数、对象时系统会自动给他们分配内存
2)内存使用:操作这些变量、函数、对象就是使用内存
3)内存回收:使用完毕后,垃圾回收器自动回收不再使用的内存
注意:
全局变量一般不会被回收,只有关闭页面的时候才会被回收
局部变量不使用就会被自动回收
3.2常见的浏览器垃圾回收算法(引用计数法、标记清除法)
3.2.1引用计数法
看一个对象是否有指向它的引用
算法:
1)跟踪记录每个值被引用的次数
2)这个值被引用一次,就会记录次数1
3)多次引用会累加
4)减少引用会使记录次数减1
5)引用次数为0时,释放内存
3.2.2标记清除法
核心:
1)标记清除法将‘不再使用的对象’定义为‘无法到达的对象’
2)就是从根部(JS的全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象都是还需要使用的
3)那些无法从根部到达的对象标记为不在使用,会被回收器自动回收
4.let,const和var的区别
1)let声明的变量会产生块级作用域,var不会
2)let声明的变量不挂载再window对象上,var声明的变量时挂载再window对象上的
3)let声明的变量不存在变量声明的提升,var声明的变量存在,但是只提升声明,不提升赋值
4)const声明常量,不可以更改赋值
5)实际开发中推荐先声明在访问变量
5.函数的参数
5.1动态参数(arguments)
arguments是函数内部内置的伪数组变量,它包含调用时传入的所有实参
// 需求:定义一个函数,求任意数的和
function getSum(){
console.log(arguments)
let sum = 0
for(let i=0; i<arguments.length; i++){
sum+=arguments[i]
}
console.log(sum)
}
getSum(1,2,3,6,4,8,6)//30
getSum(2,36,7,4,1)//50
//1)arguments是一个伪数组,只存在于函数中
//2)arguments的作用是动态获取函数的实参
//3)可以通过for循环遍历到传递过来的实参
5.2剩余参数
语法:function 函数名(...变量名){}
注意:
1)在函数内部使用的时候直接使用变量名
2)剩余参数的位置必须在函数所有参数的最后一个位置
值:是一个真数组,可以调用数组的方法包含的是函数调用时候剩余的实参
// 需求:定义函数,求任意数的和
function getSum(...arrs){
console.log(arrs)
let sum = 0
for(let i = 0; i<arrs.length; i++){
sum+=arrs[i]
}
console.log(sum)
}
getSum(1,2,3,4,5,6,7,8,9)//45
--------------------------------------------------------------
function fn(a,b,...arrs){
console.log(arrs);
}
fn(5,6)//[]
fn(1,2,3,5,4)//[3,5,4]
fn(12,54,26,98)//[26,98]
5.3展开运算符(数组的展开运算符)
语法:...数组名
作用:展开数组
经典应用场景:
1)合并数组
2)求数组的最大值和最小值(使用Math.max()方法、使用Math.min()方法)
3)对象的拷贝(浅拷贝)
//展开数组
let arr = [10,2,3,4]
console.log(...arr);//10 2 3 4
-----------------------------------------------
//合并数组
let arr = [10,2,3,4]
let arr2 = [33,66,45,99]
// 第一种方法
let arr3 = [...arr,...arr2]
console.log(arr3);//[10, 2, 3, 4, 33, 66, 45, 99]
//第二种方法
console.log(arr.concat(arr2))//[10, 2, 3, 4, 33, 66, 45, 99]
-------------------------------------------------------
// 求最大值和最小值
let max = Math.max(...arr,...arr2)
let min = Math.min(...arr,...arr2)
console.log(max,min);//99 2
-------------------------------------------------------
// 使用展开运算符进行对象的拷贝
let obj = {name:123,age:12,loc:12,}
let o = {...obj}
console.log(obj);//{name: 123, age: 12, loc: 12}
console.log(o);//{name: 123, age: 12, loc: 12}
6.箭头函数
箭头函数的基本语法:基本语法:let xxx= ()=>{}
注意:箭头函数里面有没有arguments
this指向window
let fn = () =>{
consloe.log(11)
}
----------------------------------------------------
//改造箭头函数
let fn = (a,b) =>{
console.log(a+b)//3
}
fn(1,2)
-------------------------------------------------------
//简写1:如果箭头函数的参数只有一个,那么()可以省略
let fn= a =>{
console.log(a)
}
fn(123)
---------------------------------------------------------
//简写2:如果箭头函数的参数只有一行代码,那么{}可以省略
let fn = a => console.log(a)
fn(123)
---------------------------------------------------------
//简写3:如果箭头函数的参数只有一行代码,并且使用return返回函数的结果,那么{}和return都可以省略
let sum = (a,b) => a+b
const aa = sum(1,2)
console,log(aa)
------------------------------------------------------------
//简写4:如果箭头函数的参数只有一个对象,并且return返回的是一个对象,那么{}和return可以省略,但对象需要加上()
let fn = () =>({name:'aa',age:18})
console.log(fn)
7.多维数组和多级对象如何解构
7.1多维数组解构:
语法:let(var、const)[变量名1,变量名2,...]=[元素1,元素2,...]
作用:将数组中的元素快速批量赋值给一系列的变量的简写操作
结果:是将数组的元素按照位置一一对应赋值给左边的变量
//经典应用场景 交换两个数
let a = 10
let b = 20
[b,a] = [a,b]
console.log(a,b)//20 10
-----------------------------------------------------------
//变量名多,数组元素少会出现undefined的情况
let [a,b,c,d] = [1,2,3]
console.log(a,b,c,d)//1 2 3 undefined
------------------------------------------------------------
//避免出现undefined的情况可以设置默认值
let [a,b,c,d=0] = [1,2,3]
console.log(a,b,c,d)//1 2 3 0
------------------------------------------------------------
//变量名少,数组元素多会剩余元素,使用剩余参数
let [a,b,c,...arrs] = [1,2,3,4,5,6,7,8,9]
console.log(a,b,c,arrs)//1 2 3 [4,5,6,7,8,9]
---------------------------------------------------------------
//按需导入,忽略某些返回值
let arr = [1,2,3,4,5,6]
let [a,,b,c] = arr
console.log(a,b,c)//1 3 4
7.2多级对象结构:
语法: 第一种情况: 对象解构变量名要和属性名保持一致
let {属性名,属性名}={属性名:属性值,属性名:属性值,...}
结果:是按照属性名进行解构所以变量名可以互换位置
第二种情况: 对象解构变量名和属性名不一致的情况
let {属性名:别名,属性名1:别名1,...}={属性名:属性值,属性名1:属性值,...}
结果:是按照属性名进行解构,所以变量名可以互换位置
let obj = {
name: "zs",
age: 18,
};
//第一种情况: 对象解构变量名要和属性名保持一致
let { name, age } = obj;
console.log(name, age);
----------------------------------------------------------------
// 第二种情况: 对象解构变量名和属性名不一致的情况
let { name: uname, age: age1 } = obj;
console.log(uname, age1);
//注意:以前的名不可以使用
思考题
//1)如何把3,和5解构出来
let arr = [1,2,[3,4,[5,6]]]
let [a,b,[c,d,[e,f]]]=arr
console.log(c,e)
-------------------------------------------------------------
//2) 如何将华为和小米解构出来
let shop = [{id:1,name:'小米'},{id:2,name:'华为'}]
shop.forEach(function(item){
// console.log(item);
let {id,name} = item
console.log(item.name);//小米 华为
})
--------------------------------------------------------------
//3)将猪妈妈和猪爸爸解构出来
let pig={ name:'佩奇',age:6,family:{mother:'猪妈妈',father:'猪爸爸'}}
let {name,age,family:{mother,father}} = pig
console.log(mother,father);//猪妈妈 猪爸爸
8.创建对象三种方式
8.1利用对象字面量创建对象
const o = {
name:'佩奇'
}
8.2利用new Object 创建对象
const o = new Object({
name:'佩奇'
})
8.3利用构造函数创建对象
[1]定义构造函数:function 函数名(){this.属性名=属性值}
[2]创建对象:let xxx= new 函数名()
约定:
1)构造函数的函数名首字母要大写
2)创建对象使用new关键字
注意:
使用new关键字创建的对象是结构相同,但不是同一个对象
在构造函数内部无需写return,即使写了也无效,因为构造函数内部会自动返回新创建的对象
在构造函数中this指向的是实例对象
8.3.1 new关键字干的事情:
new关键字干的事情:
【1】创建一个新的空对象
【2】将this指向这个新的空对象
【3】执行构造函数中的代码,给this添加属性和方法
【4】返回新对象
注意:使用new关键字创建对象也成为对象实例化的过程,创建的对象称为“实例对象”,简称为“实例”
//利用构造函数创建对象
function Pig(name,age){
this.name = name
this.age = age
}
// 使用new关键字创建对象也成为对象实例化的过程,创建的对象称为“实例对象”,简称为“实例”
const p = new Pig('佩奇',18)
console.log(p)
9.静态成员和实例成员的区别
注意:万物皆对象,函数既是函数,也是对象
9.1静态成员
给构造函数上面的的属性和方法称为静态成员,只能由构造函数访问,实例对象不可以访问
9.2实例成员
实例对象上面的属性和方法称为实例成员,只能由实例对象访问,构造函数不可以访问
function Pig(name,age){
// 这里的this指向的是实例对象
this.name= name
this.age=age
}
let pq = new Pig('佩奇',9)
console.log(pq);
console.log(pq.name)
Pig.eat = function(){
// 这里的this指向的是构造函数本身
console.log(this);
console.log('吃饭');
}
Pig.eat()
Pig.head = 4
console.log(Pig.head);
console.log(Pig);
10.数组的5个高阶方法(方法,作用,返回值)
10.1reduce():数组累加计算
语法:数组.reduce(function(累计值,每一项元素,[索引(index)],[原数组]){},[起始值])
参数:
【1】如果有起始值,累加值=起始值
【2】如果没有起始值,累加值是数组的第一个元素进行累计
【3】每次循环遍历数组的元素会累计到累计值里面
返回值:返回的是所求的累加结果
let arr = [1,2,3,4,5]
let result = arr.reduce(function(prev,curr){
return prev+curr
},0)
console.log(result)//15
10.2map():遍历数组
语法:数组.map(function(item,index){})
item:数组元素 index:数组索引号,下标
作用:遍历数组,对数组的每一项进行操作
返回值:返回新数组,不会影响原数组
let arr = [1, 2, 3, 4];
const map1 = arr.map(function(item){
return item+2
})
console.log(map1)//[3,4,5,6]
10.3filter():过滤
语法:数组名.filter()
作用:过滤返回满足某个条件的数组中的元素
返回值:返回新数组,不影响原数组
const arr = [1, 20, 3, 60, 5, 4, 1, 23, 5, 10];
const newArr = arr.filter(function (item) {
return item > 10;
});
console.log(newArr);// [20, 60, 23]
10.4every():(一假则假,全真为真)
语法:数组名.every(function(item,[index]){return 条件})
作用: 判断数组中所有的元素是否都符合条件
返回值: 返回值是一个布尔值,不影响原数组
如果数组中所有的元素都符合条件,那么结果是true,反之,只要有一个不符合条件,结果就是false
const arr = [1, 5, 6, 8, 7, 4, 5, 6];
const newArr = arr.every(function (item, index) {
return item > 0;
});
console.log(newArr);//true
10.5some():(一真则真,全假为假)
语法:数组名.every(function(item,[index]){return 条件})
作用:判断数组中是否符合条件的元素
返回值: 返回值是一个布尔值,不改变原数组
如果数组中有符合条件的元素,返回的是true
如果数组中所有的元素都不符合条件,返回的是false
const arr = [1, 5, 6, 8, 7, 4, 5, 6];
const newArr = arr.some(function (item, index) {
return item > 6;
});
console.log(newArr);//true
10.6find():查找数组中符合条件的第一个元素
语法:数组.find(function(item,[index]{returnt 条件})
作用:查找数组中符合条件的第一个元素
返回值:有返回值,如果有符合条件的元素,那么会返回第一个符合条件的数组元素,如果没有,返回的是undefined,不会影响原数组
const arr = [1, 5, 6, 8, 7, 4, 5, 6];
const newArr = arr.find(function (item) {
return item === 5;
});
console.log(newArr);//5
10.7forEach():遍历数组
语法:数组名.forEach(function(item,index){})
作用:遍历数组
返回值:没有返回值
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.forEach(function (item, index) {
console.log(`该元素为:${item},数组下标为:${index}`);
});
10.8indexOf():返回找到元素的索引号
语法:数组.indexOf(查找的元素)
作用:返回查找元素的索引号,如果不存在返回-1
返回值:查找元素的索引号,如果不存在返回-1
const arr = [1,2,3,4,5,6]
let aa = arr.indexOf(5)
console.log(aa);//4
10.9join拼接字符串
语法:数组名.join('以啥样子的形式拼接')
作用:拼接字符串
返回值:返回拼接好的字符串
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newArr = arr.join("-");
console.log(newArr);//1-2-3-4-5-6-7-8-9-10
console.log(arr);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
10.10reverse 颠倒数组,第一个变成最后一个,最后一个变成第一个
语法:数组名.reverse()
作用:反转数组,会改变原数组
返回值:返回新数组
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let newArr = arr.reverse();
console.log(newArr); //[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
console.log(arr);// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
10.11sort 对数组进行排序
语法:数组名.sort()
作用:对数组进行从小到大排序,会改变原数组
返回值:返回新数组
const arr = [12, 21, 30, 14, 53, 16, 27, 38, 89, 10];
let newArr = arr.sort();
console.log(newArr);//[10, 12, 14, 16, 21, 27, 30, 38, 53, 89]
console.log(arr);//[10, 12, 14, 16, 21, 27, 30, 38, 53, 89]
10.12concat 合并数组
语法:数组名1.concat(数组名2)
作用:对数组进行拼接,不会改变原数组
返回值:返回新数组
const arr1 = [1, 2, 3, 6, 5];
const arr2 = [9, 8, 5, 2, 1, 1];
const newArr = arr1.concat(arr2);
console.log(newArr);//[1, 2, 3, 6, 5, 9, 8, 5, 2, 1, 1]
console.log(arr1);//[1, 2, 3, 6, 5]
console.log(arr2);//[9, 8, 5, 2, 1, 1]
10.13from将伪数组转为真数组
语法:Array.from(伪数组)
作用:将伪数组转为真数组
返回值:返回值是一个真数组
const lis = document.querySelectorAll("ul li");
const newArr = Array.from(lis);
console.log(newArr);
10.14slice:截取数组元素
语法:数组名.slice(start(数组下标),end(数组下标))
作用:截取从start位置到end-1位置处的数据
返回值:返回截取后的数组
const arr = [1, 2, 3, 5, 4, 6, 8, 4, 2];
const newArr = arr.slice(2, 7);
console.log(newArr);// [3, 5, 4, 6, 8]
11字符串的5个方法(方法作用,返回值)
11.1trim()删除字符串两端的空白字符
语法:字符串.trim()
作用:删除字符串两端的空白字符
返回值:一个去掉两端空白字符的新字符串
const str = ' Hello world! ';
console.log(str);//" Hello world! "
console.log(str.trim());//"Hello world!"
11.2 replace()替换字符串
语法:字符串.replace('查找要替换的内容','要替换的新内容')
作用:替换字符串的内容,不会改变原字符串
返回值:返回替换后的新字符串
const str = '今天又要做核酸了'
const newStr = str.replace('又要','不要')
console.log(newStr);//今天不要做核酸了
console.log(str);//今天又要做核酸了
11.3split():将字符串转成数组
语法:字符串.split('分隔符')
作用:将字符串转成数组
返回值:是一个数组,并且不会影响原字符串
const arr = "2022-01-01";
const newArr = arr.split("-");
console.log(newArr);// ['2022', '01', '01']
console.log(arr);//2022-01-01
11.4toUpperCase():字符串转大写
语法:字符串.toUpperCase()
作用:字符串转大写
返回值:是一个新的字符串,不会影响原字符串
const str = 'aaa bbb'
const newStr = str.toUpperCase()
console.log(str);//aaa bbb
console.log(newStr);//AAA BBB
11.5toLowerCase():字符串转小写
语法:字符串.toLowerCase()
作用:字符串转小写
返回值:是一个新的字符串,不会影响原字符串
const str = 'AAA BBB'
const newStr = str.toLowerCase()
console.log(newStr);//aaa bbb
console.log(str);//AAA BBB
11.6substring():字符串的截取
语法:字符串.substring(开始的索引号,[结束的索引号])
作用:字符串截取
返回值:返回截取的部分
const str = "今天又要做核酸了";
console.log(str.substring(5, 7));//核酸
11.7 substr():字符串的截取(尽量使用substring())
语法:字符串.substr(开始的索引号,[截取的长度])
作用:截取字符串
返回值:截取的部分
注意:如果 截取的长度 为 0 或负值,则 substr 返回一个空字符串。如果忽略 截取的长度,则 substr 提取字符,直到字符串末尾。
const str = 'abcdefgfijk'
const newStr = str.substr(2,4)
console.log(newStr);//cdef
console.log(str);//abcdefgfijk
11.8indexOf()返回查找字符串的索引号
语法:字符串.indexOf(查找的元素)
作用:返回查找元素的索引号,如果不存在返回-1
返回值:查找元素的索引号,如果不存在返回-1
const str = '今天又要做核算了'
const newStr = str.indexOf('又')
console.log(newStr);//2
console.log(str);
11.9includes():判断字符串中是否有要找的字符串
语法:字符串.includes('要搜索的字符串',[检测位置的索引号])
作用:判断某个字符串是否是包含'要搜索的字符串'
返回值:返回的是一个布尔值,如果包含结果是true,否则是false
注意点: 对大小写敏感
const str = '今天又要做核算了'
const newStr = str.includes('又')
console.log(newStr);//true
console.log(str);
11.10startsWith():判断字符串是不是以要检测的字符串开头
语法:字符串.startWith('要检测的字符/字符串',[检测位置索引号])
作用:判断某个字符串是以'要检测的字符/字符串'开头
返回值:结果是一个布尔值,如果是以某个字符/字符串开头,则返回的是true,否则是false
注意点:大小写敏感
const str = '今天又要做核算了'
const newStr = str.startsWith('今')
console.log(newStr);//true
console.log(str);
11.11endsWith():判断字符串是不是以要检测的字符串结尾
语法:字符串.endsWith('要检测的字符/字符串',[检测位置索引号])
作用:判断某个字符串是以'要检测的字符/字符串'结尾
返回值:结果是一个布尔值,如果是以某个字符/字符串结尾,则返回的是true,否则是false
注意点:大小写敏感
const str = '今天又要做核算了'
const newStr = str.endsWith('今')
console.log(newStr);//false
console.log(str);
12.什么是原型
面试回答:
什么是原型链?
原型链能干吗?
为对象和属性查找指明方向
具体查找机制是怎么样
原型对象:js规定,每一个构造函数都有一个属性prototype,指向一个新对象,这个对象称为原型对象,简称原型
作用:可以将一些不变的(公共的)方法挂载在原型对象上面,所有的实例对象共享这些方法
好处:节省内存
this指向:构造函数和原型对象中this都指向实例对象
function Star(name, age) {
this.name = name;
this.age = age;
/* this.sing = function(){
console.log('唱歌');
} */
}
// 将一些不变的(公共的)方法,挂载在原型对象上面
Star.prototype.sing = function(){
console.log('会唱歌');
}
const ldh = new Star('刘德华',18)
ldh.sing()
const zxy = new Star('张学友',50)
zxy.sing()
console.log(ldh.sing === zxy.sing);//true
12.1补充求最大值和最小值的方法
Array.prototype.max = function(){
return Math.max(...this)//this都指向实例对象
}
Array.prototype.min = function(){
return Math.min(...this)//this都指向实例对象
}
const arr = [1,2,3,6,9,88]
console.log(arr.max());//88
console.log(arr.min());//1
12.2给数组扩展求和的方法
Array.prototype.sum = function(){
return this.reduce((prev.curr) => prev+curr,0)
}
const arr = [1,2,3,4,5]
console.log(arr.sum())//15
12.3constructor属性
每个原型对象上面都有一个属性constructor,指向构造函数本身
作用:能够知道原型对象是由那个构造函数产生
// 如果给原型对象直接赋值为一个对象的形式挂载方法,会丢失constructor属性,不再指向之前的构造函数
// 需要手动添加一个constructor,指向构造函数
function Star(name, age) {
this.name = name;
this.age = age;
}
// Star.prototype.sing = function(){console.log('唱歌');}
Star.prototype = {
constructor: Star,
sing: function () {
console.log("唱歌");
},
dance: function () {
console.log("跳舞");
},
movie: function () {
console.log("演电影");
},
};
const lah = new Star("刘德华", 60);
console.log(Star.prototype);
console.log(Star.prototype.constructor);
console.log(Star.prototype.constructor === Star);
12.4对象原型:__proto__
每个对象都有一个属性__proto__,称为对象原型,指向构造函数的原型对象
注意:
1)__proto__是一个js非标准的属性,只能读取,不能修改
2)[[Prototype]]和__proto__的意义是一样的
function Star(name, age) {
this.name = name;
this.age = age;
}
Star.prototype.sing = function(){
console.log('会唱歌');
}
const ldh = new Star('刘德华',18)
console.log(ldh.__proto__);
const ldhh = ldh.__proto__
ldhh.sing()//会唱歌
console.log(ldh.__proto__.constructor);//指向的是Star构造函数
console.log(ldh.__proto__.constructor === Star);//true
12.5原型链
原型对象也是对象,也拥有__proto__属性,指向上一级原型对象,这种层层向上的关系,像链条一样,称为原型链
注意:原型链也有终点,原型链的终点是Object.prototype.__proto__ === null
作用:为对象查找属性和方法指明一条道路
查找机制:
对象访问属性和方法的时候,先在对象自身上面查找,自身没有,就顺着原型链向上查找,直到找到原型链的终点,如果对象没有某个属性,结果是undefined;如果没有某个方法,结果会报错
function Star(name, age) {
this.name = name;
this.age = age;
}
Star.prototype.sing = function(){
console.log('会唱歌');
}
const ldh = new Star('刘德华',18)
/* console.log('原型对象',Star.prototype);
console.log('上一级原型对象',Star.prototype.__proto__);
console.log('上一级的上一级的原型对象',Star.prototype.__proto__.__proto__);//null */
console.log('原型对象',Star.prototype);
console.log('上一级原型对象',Star.prototype.__proto__ === Object.prototype);//true
console.log('上上级原对象',Object.prototype.__proto__);//null
12.6原型继承
子构造函数的原型对象,指向父构造函数的实例对象
子构造函数的原型对象 = new 父构造函数
function Person(){
this.head = 1
this.eyes = 2
}
// 女人
function Woman(){}
// 女人要继承Person上面的属性
Woman.prototype = new Person()
Woman.prototype.constructor = Woman
// console.log(Woman);
// 生孩子
Woman.prototype.baby = function(){
console.log('生孩子');
}
const w = new Woman()
console.log(w);//woman构造函数
w.baby()//生孩子
console.log(w.head);//1
console.log(w.eyes);//2
// 男人
function Man(){}
Man.prototype = new Person()
Man.prototype.constructor = Man
const m = new Person()
console.log(m);
console.log(m.head);
m.baby()//找不到,会报错
12.7instanceof运算符(检测的对象是否在某个构造函数上)
语法:检测的对象 instanceof 某个构造函数
作用:用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
返回值: 是一个布尔值,如果构造函数在检测对象的原型链上,结果就是ture,否则就是false
function Star(){
this.name = 'zs';
this.age = 18;
}
let arr = [1,2,3,4]
console.log(arr instanceof Array)//true
console.log(arr instanceof Object)//true
console.log(arr instanceof Star)//false
12.8如何判断一个变量是否是数组
//如何判断一个变量是不是数组
let arr1 = [1, 2, 3, 5, 4, 8];
let obj = { name: "zx", age: 18 };
// 第一种方法
console.log(arr instanceof Array);//true
console.log(obj instanceof Array);//false
// 第二中方式
console.log(Array.isArray(arr));//true
console.log(Array.isArray(obj));//false
13.说一下this的指向场景
13.1普通情况下:
①普通函数中this指向window
②在定时器中this指向的是window
③在立即执行函数中this指向的是window
④事件处理函数中this指向的是事件源
⑤在构造函数和原型对象中this指向的是实例对象
⑥在对象的方法中的this是谁调用指向谁
⑦箭头函数中没有自己的this,沿用上一级this,直到找到全局
13.2严格情况下:
开启严格模式(’use strict‘)
在严格模式下,变量必须先声明在使用否则会报错
在严格模式下,普通函数的指向为undefined
13.3改变this的指向:(call和apply和bind)
1)call:
语法:函数名.call(更改的this的值,实参1,实参2,实参3,...)
作用:【1】调用函数
【2】更改函数内部的this指向
返回值:返回值取决于函数的返回值
2) apply:
语法:函数名.apply(更改的this值,[实参1,实参2,...])
作用:
【1】调用函数
【2】更改函数内部的this指向
返回值: 取决于函数的返回值
call()和apply()的区别:传入的实参形式不同
3)bind:
语法:函数名.bind(更改的this,实参1,实参2,...)
作用:更改函数内部的this指向
返回值:返回值是一个函数,是原函数的拷贝,是一个新函数
注意点:bind不能调用函数
应用场景:如果仅仅想要改变this指向,不调用函数可以使用bind()
三者相同点:都可以改变函数内部的this指向
三者区别:
call和apply会调用函数,并且改变函数内部this指向
call和apply传递的参数不一样,call传递的参数arr1,arr2,...形式 apply必须数组形式[arr1,arr2,...]
bind不会调用函数,可以改变函数内部this的指向
call和apply的返回值取决于函数的返回值,bind的返回值是一个新的函数
主要应用场景:
call调用函数并且可以传递参数
apply经常跟数组有关系,比如借助数学对象实现数组最大值最小值
bind不调用函数,但是还想改变this的指向,比如改变定时器内部的this指向
14.浅拷贝
如果对象的属性值是简单数据类型,拷贝的是值
如果对象的属性值是复杂数据类型,拷贝的是地址
注意:浅拷贝拷贝的是单层对象,如果是多层对象就会有问题
const obj = {
name: "zs",
obj: 18,
hobbies: ["吃饭", "睡觉", "打豆豆"],
};
// 第一种方式 ...(展开运算符)
(function () {
const o = { ...obj };
o.name = "李四"; //不会改变原对象的内容
o.hobbies[0] = "吃零食"; //因为它拷贝的是地址,所以会改变原内容
console.log(o);
console.log(obj);
})();
// 第二种方式 Object.assign(目标对象,源对象)
(function(){
const o = {}
Object.assign(o,obj)
o.name = '王五'
o.hobbies[2] = '敲代码'
console.log(o);
console.log(obj);
})();
// 第三种方式 for ... in
(function(){
const o ={}
for(let key in obj){
// 键:key 值:obj[key]
o[key]=obj[key]
}
o.name = 'scsc'
o.hobbies[0] = '打篮球'
console.log(o);
console.log(obj);
})();
15.递归函数
函数在内部自己调用自己,就称为递归函数
注意:递归一定要有终止的条件return,否则会产生死递归,结果会报错,报错栈溢出
// 需求:使用递归实现,让函数里面的代码执行6次
(function () {
let i = 0;
function fn() {
i++;
console.log(`第${i}次`);
if (i >= 6) return;
fn();//自己调用自己
}
fn();
})();
16.深拷贝
如果对象的属性值是一个数组,拷贝的是一个全新的数组
如果对象的属性值是一个对象,拷贝的是一个全新的对象
const obj = {
name: "zx",
age: 18,
hobbies: ["吃饭", "睡觉", "打豆豆",{address:'山西省'}],
family: {
baby: "佩奇",
},
};
// 实现深拷贝
// 注意:Array和Object不可以互换位置,因为万物皆对象(数组也是对象,直接进入Object里,不会执行下边的代码)
let o ={}
for(let key in obj){
// 键:key 值:obj[key]
let item = obj[key]
// 判断属性值是否是一个数组
if(item instanceof Array){
// 证明是一个数组
// 开辟一个空数组
o[key] = []
// 循环遍历item,将item的每一项添加到空数组中
for(let k in item){
// 索引为:k 元素为item[k]
o[key][k] = item[k]
}
}else if(item instanceof Object){
// 判断属性值是不是一个对象
// 证明是一个对象
// 开辟一个空对象
o[key] = {}
// 循环遍历item,将item的每一项添加到空对象中
for(let k in item){
o[key][k] = item[k]
}
}else{
// 属性值是一个简单数据类型
o[key] = item
}
}
o.hobbies[0] = '敲代码'
o.family.baby = '猪妈妈'
console.log(o);
console.log(obj);
16.1递归实现深拷贝
const obj = {
name: "zx",
age: 18,
hobbies: ["吃饭", "睡觉", "打豆豆", { address: "山西省" }],
family: {
baby: "佩奇",
},
gender: "男",
};
let o = {};
// 递归实现深拷贝
function deepCopy(newObj,oldObj){
for(let key in oldObj){
const item = oldObj[key]
if(item instanceof Array){
// 证明属性值item是一个数组
newObj[key] = []
// 将item中额属性值取出来添加到空数组中
deepCopy(newObj[key],item)
}else if(item instanceof Object){
// 证明属性值item是一个对象
newObj[key] = {}
// 将item中的属性名和属性值取出来添加到空对象中
deepCopy(newObj[key],item)
}else{
// 证明属性值item是一个简单数据类型
newObj[key] = item
}
}
}
deepCopy(o, obj);
o.hobbies[3].address='山东省'
console.log(o);
console.log(obj);
16.2使用lodash实现深拷贝
引入lodash.min.js调用_.cloneDeep()实现深拷贝
<script src="./lodash.min.js"></script>
<script>
const obj = {
name: "zx",
age: 18,
hobbies: ["吃饭", "睡觉", "打豆豆", { address: "山西省" }],
family: {
baby: "佩奇",
},
gender: "男",
};
// 使用loadsh的cloneDeep实现深拷贝
const o = _.cloneDeep(obj)
o.hobbies[3].address = '北京'
console.log(o);
console.log(obj);
16.3利用JSON实现深拷贝
//使用JSON.stringify(obj)转成字符串再转对象
const obj = {
name: "zx",
age: 18,
hobbies: ["吃饭", "睡觉", "打豆豆", { address: "山西省" }],
family: {
baby: "佩奇",
},
gender: "男",
};
const o = JSON.parse(JSON.stringify(obj));//JSON.parse转为对象
o.hobbies[3].address = '北京'
console.log(o);
console.log(obj);
17.JS的异常处理
17.1throw抛出异常
throw抛出异常,后面的程序会中断执行
语法:throw new Error('错误信息提示')
let str = 'abcdef'
// 需求:定义函数,实现将字符串转大写
function fun(str){
if(!str){
// throw抛出异常,后面的程序会中断执行
// throw后面跟的是错误信息
// throw一般要搭配Error使用
throw new Error('您没有传参,请把参数传递过来')
}
console.log(str.toUpperCase());
}
fun()
17.2try-catch捕获异常(错误代码会被拦截,不会中断程序)
try{
// 放容易出错的代码
const p = document.querySelector('.p')
p.style.color = 'red'
}catch(err){
// 如果try里面的代码会报错,会被catch拦截,但是代码不会中断程序继续执行
console.log(err);
console.dir(err);
console.log(err.message);
}finally{
// 无论程序是否会发生错误,finally里面的代码都会执行
console.log(22);
}
console.log(11);
17.3debugger断点调试
const arr = [1, 2, 3, 4, 5, 6];
arr.forEach((item, index) => {
debugger;
console.log(`每一项数组元素${item}`);
debugger;
console.log(`数组的下标${index}`);
});
18.对象获取属性名和值的方式都有哪些方式
//Object.values(obj);获取对象的属性值
//Object.keys(obj)获取对象的属性名
let obj = {
name: "aa",
age: 18,
};
const o = Object.values(obj);
console.log(o);//['aa', 18]
const b = Object.keys(obj)
console.log(b);//['name', 'age']
19.节流防抖
19.1节流
节流:使用闭包的形式
核心思路:时间相减
定义: 时间连续触发,但是在n秒内只执行一次
应用场景:轮播图,屏幕缩放resize,鼠标移动事件,滚动条滚动等
// 需求:鼠标移动到盒子上,让数字+1
// 获取元素
const box = document.querySelector(".box");
let i = 0;
// 事件处理函数
function move(e) {
// 数字加1
box.innerHTML = ++i;
console.log(this);//要求这里的this指向事件源
console.log(e);
}
// 定义节流函数
function throttle(fn, delay) {
// 定义起始时间
let startTime = 0;
return function (...args) {
// console.log(e);
console.log(this);//指向事件源
// 获取当前时间
const currentTime = Date.now();
// 时间相减
if (currentTime - startTime >= delay) {
// 执行函数
// fn();
// fn.call(this,e)
fn.apply(this,args)
// console.log(this);
// 修改起始时间
startTime = currentTime;
}
};
}
box.addEventListener("mousemove", throttle(move, 500));
19.2防抖
防抖:
定义:事件触发之后n秒执行一次,如果在n秒内连续触发事件,则要重新计时
核心思路:使用定时器清除和开启
注意:防抖在n秒内多次触发事件,只执行最后一次事件
// 需求:鼠标在盒子移动+1
const box = document.querySelector(".box");
let i = 0;
function move(e) {
box.innerHTML = ++i;
console.log(this);
console.log(e);
}
// 定义防抖函数
function debounce(fn, delay) {
// 定义定时器
let timerId = null
return function(...args){
// console.log(this);
//判断是否开启了定时器
if(timerId) clearTimeout(timerId)
// 开启定时器
timerId = setTimeout(()=>{
fn.apply(this,args)
},delay)
/* timerId = setTimeout(function(){
fn()
},delay) */
}
}
box.addEventListener("mousemove", debounce(move,500));
19.3使用lodash节流防抖
const box = document.querySelector(".box");
let i = 0;
function move() {
box.innerHTML = ++i;
}
// 使用lodash实现节流
// box.addEventListener("mousemove", _.throttle(move, 800));
// 使用lodash实现防抖
box.addEventListener("mousemove", _.debounce(move, 800));
#