1.防抖函数
//在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时 场景:输入框联想输入
//防抖函数,可用于登录使用,在用户输入完最后的时间为准,隔着传入时间执行一次
//运用了闭包 推荐使用lodash中的_.debouch()和_.throttle()
<input type="text" id="ipt">
function debouch(cb, delay = 1000) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
cb.apply(this, arguments)//arguments无所谓,就是事件源
}, delay)
}
}
const ipt = document.querySelector("#ipt")
let newFn = debouch(function (e) {
console.log(e.target.value);
}, 1000)
ipt.addEventListener("input", newFn)
2.节流函数
//节流函数 每隔固定时间执行一次,如水龙头滴水一样,jd搜索就是这么做的 抢购
function throttle(cb, delay=1000) {
//设置节流阀
let lock = true
return function () {
if (lock) {
setTimeout(() => {
cb.apply(this, arguments)
lock = true
}, delay)
lock = false
} else {
return false
}
}
}
const ipt = document.querySelector("input")
ipt.addEventListener("input", throttle(function (e) {
console.log(e.target.value);
}, 500))
3.拷贝
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
const user = {
name: "小貂蝉",
age: "18",
show: function () { },//使用JSON.parse(JSON.stringify())进行深拷贝会丢失
a: undefined,//会丢失
b: Infinity,//null
c: -Infinity,//null
date: new Date(),//对象格式被处理成字符串
reg: /abc/,//{}
company: {
name: "因你太美",
address: "彩云之南"
}
}
//JSON.parse(JSON.stringify(user))实现深拷贝
let user1 = JSON.parse(JSON.stringify(user))
let user2 = _.cloneDeep(user)
// 浅拷贝只拷贝一层
const info = {
name: "赛丽亚",
age: 20
}
//for...in 循环
let tempInfo = {}
for (let key in info) {
tempInfo[key] = info[key]
}
//Object.assign
const tempInfo2 = Object.assign({}, info)
4.ES5,ES6的类
//ES5
function Person(name, age) {//构造函数
this.name = name,
this.age = age
}
Person.prototype.eat = function () {//往原型上挂方法
return "我是原型上的方法"
}
Person.way = function () {//往自己身上挂方法,只有类自己才可以使用
return "我是静态方法"
}
const son = new Person("小貂蝉", 20)
console.log(son);
//ES6
//申明一个类 class是关键字
class Person { num = 10;//属性值写死的,会自动成为实例对象(自动挂在new出来的实例对象上)
constructor(name, age) {//构造器
this.name = name,
this.age = age
}
eat() {
return "我是原型方法"
}
static way() {
return "我是静态方法"
}
}
const son = new Person("李寻欢", 22)
console.log(son);
5.继承
原型继承
//把父类的实例作为子类的原型
//缺点 子类的实例共享了父类构造函数的引用属性 不能传参
let person = {
friends: ["a", "b", "c"]
}
let p1 = Object.create(person)
p1.friends.push("子类新增") //子类的实例共享了父类构造函数的引用属性
console.log(person);// {friends: ["a", "b", "c","子类新增"]}
//子类的实例共享了父类构造函数的引用属性
组合继承
//在子函数中运行父函数,但是要利用call把this指向改变一下,
//让子函数的prototype指向new Father(),使Father的原型中的方法也得到继承,
//最后改变Son的原型中的constructor
//缺点 调用了两次父函数的构造函数,造成了不必要的消耗,父类方法可以复用
//优点 可以传参,不共享父类引用属性
function Father(name) {
this.name = name
this.hobby = ["唱", "跳", "篮球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.constructor = Son
let son = new Son("哥哥", 20)
console.log(son);//Son{name:"哥哥",hobby = ["唱", "跳", "篮球"],age:20}
寄生继承
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function () {
return "干饭"
}
function Student(name, age, hob) {
// this.name = name
// this.age = age
//第一步 继承父类的属性
Person.call(this, name, age)
this.hob = hob
}
//第二步,让子类的显示原型成为父类的实例对象
Student.prototype = Object.create(Person.prototype)
//找回构造器
Student.prototype.constructor = Student
Student.prototype.sex = "女"
function Monkey(name, age, sno) {
// this.name = name
// this.age = age
Person.call(this, name, age)
this.sno = sno
}
Monkey.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Monkey.prototype.job = function () {
return "cv攻城狮"
}
let s = new Student("小貂蝉", 20, "唱跳")
let m = new Monkey("李寻欢", 22, "rap篮球")
console.log(s);
console.log(m);
寄生组合式继承
//寄生组合式继承
function Father(name) {
this.name = name
this.hobby = ["唱", "跳", "篮球"]
}
Function.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
let son = new Son("小貂蝉", 20)
console.log(son);//Son{name:"哥哥",hobby = ["唱", "跳", "篮球"],age:20}
ES6继承
class Father {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
return "吃鸡蛋"
}
static hobby() {
return "唱跳rap篮球"
}
}
class Son extends Father {
constructor(name, age, tel) {
super(name, age)//super必须写在第一行,继承父类的属性和方法
this.tel = tel
}
study() {
return "太美辣"
}
}
const s = new Son("只因", 22, 10001)
console.log(s);
6.this指向
//全局指向window
console.log(this);
//函数中的this,谁调用指向谁
function fn() {
console.log(this);
}
fn()//window 相当于window.fn()
//对象中的方法的this,谁调用指向谁
const obj = {
name: "小貂蝉",
age: 20,
show: function () {
console.log(this);
}
}
obj.show() //obj这个对象
//构造函数中的this,指向实例对象
function Person(name, age) {
this.name = name
this.age = age
console.log(this);
}
const p = new Person("李寻欢", 22)
//事件处理函数中的this,指向事件源
btn.addEventListener("click", function () {
console.log(this);//事件源
})
//定时器中的this,指向window
setTimeout(function () {
console.log(this);//过一秒自己调用
}, 1000)
//箭头函数中的this指向上一级(箭头函数没有this,它的this绑定定义函数时所处的作用域)
btn.addEventListener("click", () => {
console.log(this);//window
})
7.递归
1.函数自己调用自己 2.需要临界条件,有出口 优点:机构清晰,可读性强(斐波那契数列,兔子函数(F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3))) 缺点:运行效率较低,占用内存,容易爆栈,造成内存溢出
//阶加求和
//自己调用自己,有临界点给出口
function sum(num) {
if (num === 1) {
return 1
} else {
return num + sum(num - 1)
}
}
let r = sum(5)
console.log(r);
8.Promise
Promise是同步的,Promise 是异步编程(可以解决回调地狱)的一种解决方案,es6新增的
- 静态方法
- resolve()//让状态从进行中pending==》已成功
- reject()//让状态从进行中==》已失败
- all()//用途:可以并发多个ajax,同时执行多个Promise,每个都成功,才是成功,才可以拿到最终的结果是一个数组,有一个失败,就直接结束
- allSettled()//不管成功失败返回一个对象数组,包含状态(成功或者失败)及其值
- any()//只有有一个Promise成功,立即返回
- race()//最快的切换状态完成就结束,无论是结束还是失败
- 原型方法
- then() //成功(走resolve)
- catch()//捕获错误 失败(走reject)
- finally()//无论成功失败都会走
const p=new Promise((resolve,reject)=>{
if(true){
resolve("成功")
}else{
reject("失败")
}
})
//p.then返回的是一个pimise对象
p.then(res=>{
console.log(res)//上一步调用了resolve,就会触发这个函数
},err=>{
console.log(err)//上一步调用了reject,就会触发这个函数
})
//--------------------
p.then(res=>{
console.log(res)//上一步调用了resolve,就会触发这个函数
})
.catch(err=>{//捕获错误
console.log(err)//上一步调用了reject,就会触发这个函数 和.then的第二参数回调是一样的功能
})
.finally(()=>{
console.log("无论成功失败,都会走一次")
})
//----------------------
const p1=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("1")
},1000)
})
const p2=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("2")
},2000)
})
const p3=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("33")
},5000)
})
Promise.all([p1,p2,p3]).then((value)=>{
console.log(value)//5秒以后都成功返回的数据["1","2","3"] 并发发送ajax
})
Promise是同步任务,promise.then(catch,finally)是微任务,async/await也是微任务
9. async/await
是Promise对象的语法糖,是ES7新增的,可以配合try/catch捕获错误 async是声明了这函数中有异步操作,会返回一个Promise对象(同步任务) await是用来等待后面表达式的完成
async function fn(){
let p= new Promise((resolve,reject)=>{
reject("Error")
})
try{
let res= await p
}catch(e){
console.log(e)
}
}
10.原型与原型链
作用:添加共享方法 原型
- 每个函数(特指构造函数),都有一个属性prototype就是原型(显示原型)
- prototype是一个对象,添加在里面的属性和方法,被所有实例对象所共享
- 实例对象(new出来的),都有一个_proto__(隐式原型),指向构造函数的prototype
- prototype有一个属性constructor(构造器),指向构造函数本事
原型链
- prototype也是一个实例对象,也有一个_proto__指向Object.prototype
- Object.prototype也是一个实例对象,也有一个_proto__指向null
- Object.prototype也有一个属性constructor,指向Object本身
11.闭包和垃圾回收机制
函数跨作用域访问变量,就会形成闭包,闭包是一种作用域的体现。父函数嵌套子函数,子函数跨作用域访问父函数的变量,把子函数返回或挂载在全局。可以实现早期的模块化,闭包可以解决循环定时器问题,循环绑定事件问题;闭包的优点:把变量隐藏在函数内部,变量私有化,避免全局污染,缺点:过多使用闭包,形成闭包的变量不会被释放,造成内存开销过大,甚至内存泄漏
//第一种
function spendMoney=(function(){
let money=500
function spendMoney(){
console.log(money)
money-=100
}
//把子函数返回
return spendMoney
})
spendMoney();
//第二种
(function(){
let money=500;
function spendMoney(){
console.log(money)
money-=100
}
//把子函数挂在全局window上
window.spendMoney=spendMoney
})()
小憩一下,原型链,闭包,Promise,江湖人称自然界的'珠穆朗玛峰,乔戈里峰,干城章嘉', 翻过这三座山,他们就会听到你的故事
12.call,apply和bind
都是改变this指向,第一个参数是谁,this指向谁
函数体.call()立即执行 第二个参数是罗列式
函数体.apply()立即执行 第二个参数是一个数组
函数体.bind()不会立即执行,会返回一个新的函数,所以需要调用(后面加个括号,函数体.bind()())才能执行;第二个参数也是罗列式
let arr=[1,8,5,2,3]
Math.max.apply(null,arr)//8
let name="张三",age="18";
let obj={
name="李四",
objAge:this.age,
myFn:function(from,to){
console.log(this.name+"年龄"+this.age,"来自"+from+"去往"+ to);
}
}
let db={
name:"小貂蝉",
age:"20"
}
obj.myFn.call(db,"成都","上海")//小貂蝉年龄20来自成都去往上海
obj.myFn.apply(db,["成都","上海"])//小貂蝉年龄20来自成都去往上海
obj.myFn.bind(db,"成都","上海")()//小貂蝉年龄20来自成都去往上海
obj.myFn.bind(db,["成都","上海"])()//小貂蝉年龄20来自成都,上海去往undefined
13.手写call,apply和bind
**call**
Function.prototype.maCall=function(context=window){
//先判断调用的maCall是不是一个函数,this指向调用者(调用maCall的)
if(typeof this !=="function"){
throw new TypeError("Not a Function")
}
//保存this
context.fn=this
//保存参数
let args= Array.from(arguments).slice(1)//Array.from把伪数组对象转为数组
//调用函数
let result = context.fn(...args)
delete context.fn
return result
}
**apply**
Function.prototype.myApply=function(context=window){
if(typeof this !=="function"){
throw new TypeError("Not a Function")
}
let result;
context.fn=this
if(argument[1]){
result=context.fn(...arguments)
}
else{
result=context.fn()
}
delete context.fn
return result
}
**bind**
Function.prototype.myBind=function(context){
//判断是否是一个函数
if(typeof this !=="function"){
throw new TypeError("Not a Function")
}
//保存调用bind的函数
const _this=this
//保存参数
const args=Array.prototype.slice.call(argument,1)
//返回一个函数
return function F(){
//判断是不是new出来的
if(this instanceof F){
//如果是new出来的
//返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
//如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
14.函数柯里化
//函数柯里化就是我们给一个函数传入一部分参数,此时就会返回一个函数来接收剩余的参数。
//https://www.jb51.net/article/234665.htm
//未使用柯里化
function sum(a,b,c){
return a+b+c
}
console.log(sum(1,2,3))//6
//使用柯里化
function sum(a){
return function(b){
return function(c){
return a+b+c
}
}
}
console.log(sum(1,2,3))//6
//上面函数可以简写
const sum=a=>b=>c=>a+b+c
console.log(sum(1,2,3))//6
//实现原理
function curry(fn){
return function currying(...args) {
if(args.length>=fn.length){
return fn.apply(this,args)
}else{
return function(...args2){
return currying.apply(this,args.concat(args2))
}
}
}
}
15. for...in,for...of,for,forEach的区别与Object.keys
对象的数据属性有{
value:属性值,
writable:true/false,//控制是否可修改
enumerable:true/false,//控制是否可以被for in 遍历,可以就是**可枚举属性**,不可以就是不可枚举属性
configurable:true/false,//控制是否可删除,控制是否可修改前两个特性,一旦改为false就不可逆
get和set函数
}
for...in 更适合遍历对象,遍历的是索引Index(键名),会遍历手动添加的键名和原型上的属性和方法,**会遍历整个原型链**,可以实现浅拷贝
let tempObj={}
for(let key in obj){
tempObj[key]=obj[key]
}
Object.keys()用于获得由对象属性名组成的数组,可与数组遍历(forEach,for)相结合使用
let person={
name:"张三",
age:20,
sex:"男"
}
Object.keys(person).forEach(e=>{
console.log(e,"+",person[e]) // name+张三 age+20 sex+男
})
//Object.keys性能更优
for...of ES6新增,更适合遍历数组,只会遍历的键值,获取对象的value值,可以配合return,break中途跳出循环
for 可以控制循环起点,可以中途跳出,可以修改索引
forEach es5提出,循环无法中途跳出,无法对循环对象中的内容进行增删改操作,循环中不能修改索引(底层隐式控制index自增,无法操作)break 命令或 return 命令都不能奏效(借助try/catch可以跳出),不能循环伪数组(argument),没有返回值,默认return undefined
性能方面:for>forEach(有回调函数(v,i,arr)和上下文(this))>map(创建一个新数组,产生新的内存空间)
遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法