javascript-cnblog
1. 原型和原型链
1.1 原型
- js中,函数可以有属性,每个函数都有一个特殊的属性叫做原型prototype(原型)
1.2 原型链
- 当我们访问某个对象的属性时,如果该对象本身没有,就会到对象的原型中去寻找定义,如果原型也没有,就再向原型对象的原型中寻找,依此类推,如果最顶层的对象也没有,则返回undefined,整个查找过程为链式,连接对象和对象的原型的链条就叫做原型链
1.3 原型链存在的意义
- 可以使实例对象共享原型的属性和方法,节省代码量,减小内存的开销
2. 作用域
- 代码在运行时某些特定部分中的变量,函数,对象,代表对上述资源的可访问性
- 分类
- 全局作用域
- 在代码的任何地方都能访问到的变量,对象,函数
- 局部作用域
- 函数作用域
- 块级作用域
- {} 包裹起来的区域
- 全局作用域
2.1 作用域链
- 在某一个地方访问一个变量,函数,或对象时,首先在当前作用域中查找,如果没有找到,则向父级的作用域中进行查找,以此类推,如果最终没有找到,则报undefined的错误,这种一层层嵌套的关系,就叫做作用域链
2.2 作用域存在的意义
- 变量隔离,不同作用域下的同名变量互不干扰
3. 闭包
-
能够访问其他函数内部变量的函数,称为闭包
-
即一个函数在调用时,其内部找不到相应需要的变量或对象,需要在作用域链上寻找,找到后会维持对该变量的引用(一个函数和对其周围环境的引用捆版在一起)
-
产生的问题
- 维持对父级作用域变量的引用,会导致父级作用域中的变量常驻内存
-
使用的场景
-
防抖节流函数实现
-
事件函数的封装
-
循环注册事件函数:基本思想:如果循环变量i为全局变量,则循环注册最终事件处理函数引用的为同一个i
-
闭包:每个循环都是一个立即执行函数,函数里面注册事件,这里的i(for循环的i->立即执行函数形参->事件处理函数的i引用形参)
- 则事件处理函数与立即执行函数作用域的变量i的形成的闭包
-
隐藏变量
-
外层函数,类似java的类class,其中的局部变量==》java的private 变量
-
提供一些修改局部变量的方法,return 回去(setPrivateLoacl,getPrivateLocal)(目的是为了外层函数外,即全局作用域中可以通过方法进行修改)
-
return不是必须的,之所以return 无非是为了提供一个操作私有数据的接口
-
例如上一个循环注册事件,无需return
-
-
4. call,bind,apply 方法
- 相同点:都可以改变this的指向
- 不同点 :apply传递的第二个参数为数组(即如果有1个或多个参数,以数组的形式传递)
- call 和 apply改变this指向同时也会调用函数,返回值为函数的调用
- bind 返回的是改变this指向后的新函数
4.1 call的使用场景
- 判断数据类型
- Object.prototype.toString.call(this指向(调用者))
- 类数组(伪数组,对象表示的数组)转数组
- Array.prototype.slice.call(调用者)
4.2 apply的使用场景
- 关键,传递的第二个参数为数组
- 求最大值,最小值(Math.max(...array))
- 使用apply,(Math.max.apply(null,array))
4.3 bind的使用场景
-
改变this指向的同时不立即调用
-
事件处理的回调函数,因为事件处理函数直接调用,相当于
-
function callback(){ // 处理逻辑 } -
function里面的this为undefined
-
this.callback=this.callback.bind(this) ,绑定this
-
// * 判断数据类型 // * 类数组转数组 const array = [1, 2, 3, 4]; const type = array.toString() console.log(array); // const type = Object.prototype.toString.call(array); console.log(`type`, type) // const arrayLike = { // 0: "name", // 1: "age", // 2: "gender", // length: 3 // } // const res = Array.prototype.slice.call(arrayLike); // console.log(`res`, res) // // ["name","age","gender"] // apply 对给定数组求最大值/最小值 // const array = [1,2,3,4,5]; // const max = Math.max.apply(null,array) // const min = Math.min.apply(null,array) // // Math.max(1,2,3,4,5) // console.log(`max`, max) // console.log(`min`, min) // bind // class App extends React.Component { // constructor(props) { // super(props); // this.name = 'freemen' // this.handleClick = this.handleClick.bind(this) // } // handleClick(){ // console.log(`this.name`, this.name) // } // render(){ // return ( // <button onClick={this.handleClick}> // 点击 // </button> // ) // } // }
5.数组去重
5.1 前置知识
- 数组的indexOf()方法
- 参数数组项,返回值,数组项的下标
- filter方法
- 返回值新数组
- sort
- 参数(a,b),return a-b 从小到大排,b-a 从大到小排
- 返回值排序后的新数组
- push
- 参数:需要push进去的项
- 返回值:数组push后的长度
- 改变了原数组
5.2 filter +indexOf数组去重
array.filter((item,index)=>{ return array.indexOf(item)===index })
- 原理
- for循环一遍数组
- 循环每一项时看该项是不是在之前出现过,即判断当前项的下标,与第一次出现相同项的下标是否相同
5.3 sort+移动零原理
// 先排序 array=array.sort() let arr=[] for(let i=0;i<array.length;i++){ if(array[i]!==array[i-1]){ arr.push(array[i]) } }
- 原理
- 对原数组进行排序,从小到大
- 则相同的元素会成组出现,如111222333
- 我们需要从中获取到我们需要的部分
- 即每组的分界点的元素
- undefined和1
- 1和2
- 2和3
- 即每组的分界点的元素
5.4 Set + 解构赋值
[...newSet(array)]
- Set不允许出现重复元素
5.5 set+Array.from
Array.from(new Set(array))
对象数组去重(根据某个key)
- 临时对象缓存key值实现
- 存储key值
- 没有则存储一个新的key值
- 有则直接过滤掉
const array=[{name:'666',age:20},{name:'999',age:20}] // 去除age相同的 let template={} let result=[] for(let i=0;i<array.length;i++){ if(template[array[i]['age']]){ continue; } template[array[i]['age']]=true; result.push(array[i]) }
- reduce函数实现
const array=[{name:'666',age:20},{name:'999',age:20}] // 去除age相同的 let template={} array.reduce((prev,curr)=>{ if(!template[curr['age']]){ template[curr['age']]=true prev.push(curr) } return prev; },[])
- prev最初为[] 数组
6.数组最大值
- Math.max()
- Math.max(...array)
- Math.max.apply(array)
- Array.reduce()
- array.reduce((prev,curr)=>{
- return curr>prev? curr:prev
- })
- array.reduce((prev,curr)=>{
- sort+lastIndexOf()
- 排序后找最后一个
7.节流
- 单位事件内触发多次事件,第一次有效
- 场景
- 轮播图单位事件内多次被点击
- movemouse事件,获取鼠标的位置,控制鼠标样式图标跟随移动
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 实现节流函数 function throttle(fn, timeOut) { // 定义一个timer let timer = null // 返回一个函数调用,注意,这里形成的闭包 return (event) => { if (timer) { return } timer = setTimeout(function() { // 计时结束后的逻辑 clearTimeout(timer) timer = null fn(event) }, timeOut) } } // 监听窗口resize事件 window.onresize = throttle((event) => { console.log(event); }, 1000) </script> </body> </html>
8.防抖
- n秒内多次触发同一事件,最后一次有效
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <input type="text" id="q"> <script> // 实现防抖 const debounce = (fn, timeOut) => { let timer = null; return () => { // 每次触发,都会进入这里 // 清除原先的定时器,当不进入时,最后一个就生效了 clearTimeout(timer) timer = null // 形成了闭包 timer = setTimeout(function() { // 指向搜索的逻辑 fn() }, timeOut) } } const search = document.getElementById('q') search.oninput = debounce(() => { console.log('saerch'); }, 1000) </script> </body> </html>
9.数组拍平
-
数组降维
-
实现方法
- reduce函数
- es6的flat函数
- while循环+Array
9.1 reduce
- 原理:递归调用
// reduce函数 // function flatter(array) { // return array.reduce(function(prev, current) { // return prev.concat(Array.isArray(current) ? flatter(current) : current) // }, []) // }
9.2 flat函数
- Infinity为数组的维数,Infinity为最大值
array.flat(Infinity)
9.3 while循环+展开运算符
- 每次循环,判断当前数组是否存在数组元素
- 有就展开,然后再合并
while(array.some(Array.isArray)){ array=[].concat(...array) }
10.实现new操作符
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // 构造函数 const TMap = function (options) { this.name = options.name; this.address = options.address; } // const map = new TMap({ // name: 'tmap', // address:"BJ" // }); // console.log('map :>> ', map); //1. 实例化对象 2.返回值的问题: 构造函数中如果有值返回 那实例化后的对象就是这个返回值。 const ObjectFactory = (...args) => { const obj = {}; // [].shift.call 等价于 Array.prototype.shift.call // 因为args为伪数组 const Constructor = [].shift.call(args) // 实例对象的原型链和构造函数的原型指向相同 obj.__proto__ = Constructor.prototype; // obj.Constructor(args) Constructor==>Tmap // this.name = options.name; ==>obj.name==args.name // this.address = options.address; ==>obj.address=args.address // 这里函数并无返回值,即Tmap没有写返回值,如果写了,返回的就不是obj这个对象了 const ret = Constructor.apply(obj, args); console.log('ret', ret, obj); return typeof ret === 'object' ? ret : obj; } const map = ObjectFactory(TMap, { name: "MAP", address: "BJ" }); console.log('map :>> ', map); </script> </body> </html>
11. 实现一个bind函数
function origin(a, b) { console.log(this.name); console.log([a, b]); } const obj = { name: "freemen" } // const func = origin.bind(obj,2); // func(1) Function.prototype.bindFn = function() { // 1 获取源函数 // this为函数的调用者,即origin()函数 const fn = this; // 2 获取目标对象 // 获取第一个参数,即需要修改到的this指向 // const func = origin.bind(obj,2); const obj = arguments[0]; // 3. 获取源函数参数列表 const args = [].slice.call(arguments, 1) // 4. 返回函数 return function() { // 5 获取返回函数的参数列表 // func(1) // 即返回的新函数(this指向已经修改)在调用时传递的参数 const returnArgs = [].slice.call(arguments); // 6 执行源函数 fn.apply(obj, args.concat(returnArgs)); } }
12. apply 和 call函数的实现
- 原理,都是在目标对象obj(修改后this指向)中将调用的(需要修改指向)方法加入obj中
- obj的调用作为返回结果
const obj = { name: "freemen" } function testFunc(a,b){ console.log('a :>> ', a); console.log('b :>> ', b); console.log('this.name :>> ', this.name); } // testFunc.call(obj,'a','b') // testFunc.apply(obj,['a','b']) const core = (context,args,_this) => { args = args || []; const key = Symbol(); context[key] = _this; /* obj={ key:testFunc } obj.key(args) // 调用者是不是变成了obj this指向改变 */ const result = context[key](...args) delete context[key] return result; } Function.prototype.callFn = function(context,...args){ // context 为this指向,obj对象 // this为方法的调用者 即testFunc return core(context,args,this); } Function.prototype.applyFn = function(context,args){ return core(context,args,this); } testFunc.callFn(obj,'a','b'); testFunc.applyFn(obj,['a','b']); //
13. instanseof 运算符
- 用于检测构造函数的prototype属性是否出现在实例对象的原型链上
- 实现原理
- 实例对象的原型链 __ proto __
- 构造函数的prototype显示原型
- 判断对象的 __ proto __ === prototype,找到返回,找不到,对象的原型链上移
function Persion(){ this.name = "freemen" } const obj = new Persion // console.log(obj instanceof Persion) // instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上 function instance_of(Obj, Constructor){ let implicitPrototype = Obj.__proto__; // 获取实例对象的隐式原型 let displayPrototype = Constructor.prototype; // 获取构造函数的prototype 属性 // while 循环 -> 在原型链上不断向上查找 while(true){ // 直到 implicitPrototype = null 都没找到, 返回false if(implicitPrototype === null){ return false // 构造函数的 prototype 属性出现在实例对象的原型链上 返回 true }else if (implicitPrototype === displayPrototype){ return true } // 在原型链上不断查找 构造函数的显式原型 implicitPrototype = implicitPrototype.__proto__ } } const has = instance_of(obj,Persion) console.log(`has`, has)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!