《JavaScript》JavaScript进阶知识点(一)

能实现要求,但是又带来了一个新问题,变量i定义到了全局环境重,我们不确定i这个变量会不会在其他部分被使用过,重新定义会不会有什么问题,即使假设这部分没问题,也不确定这个变量是不是在团队其他成员使用过了,如果使用过,合并代码的时候,会出现各种异常,到时候难免又是加班加点排查错误,因此,肯定不能将i定义在全局环境中,因为其风险我们不可控,那怎么办?在立即执行函数中说了,因为作用域的关系,函数体内的变量,不会影响到父级作用域,因此,改变一下,在外层我们在嵌套一个output函数,比如

function output(){

var i = 0;

function count(){

i++;

return i;

}

}

这么写是没什么问题,变量i也不在全局环境中了,但是我们这么访问这个count函数呢?这么写访问不了,那就通过return返回出去

function output(){

var i = 0;

return function count(){

i++;

return i;

}

}

//因为返回的是一个函数,所以必须先定义一个变量来接收这个函数

var count = output();

//相当于

var count = function count(){

i++;

return i;

}

这么一看返回的函数名count没有存在的必要,因为返回的是一个函数,而外界肯定要接收,所以直接返回一个匿名函数就好

function output(){

var i = 0;

return function(){

i++;

return i;

}

}

//因为返回的是一个函数,所以必须先定义一个变量来接收这个函数

var count = output();

count() //1

count() //2

这下总算可以了,实现了题目中的要求,这种函数也就是我们最常见的闭包,其作用就是帮助我们保留执行结果,并且不污染全局环境

千万不要以为到这就结束了,闭包有一个非常大的缺陷,就是执行过后,这个称作闭包的函数没有被释放(我们也就是借助这一点保留了执行结果),正常情况下,函数执行了,引擎就会将函数释放掉,销毁掉,之后外界也就访问不到当前结果了,可闭包不是,它的值一直存在于当前的环境中,这也就导致了内存泄漏,又或者被“有心人”收集当前的执行结果,为了解决这种问题,等到不用的时候记的手动释放

var count = output();

count() //1

count() //2

//释放

count = null;

函数式编程


函数式编程是一种编程的规范,也可以说是一种编码风格,与函数式编程对应的是命令式编程

示例

假设现在有一个题目:有一个数组[1, 2, 3, 4],对数组进行操作,操作后,生成一个新的数组,其值是原数组的每项+1

命令式编程,就是为了达到最终效果,将执行的步骤每一步就详细的描述出来,然后让引擎去按设定好的步骤执行,比如:

//创建一个数组

let arr = [1, 2, 3, 4];

//创建一个新数组

let newArr = [];

//对老数组的每一项进行遍历

arr.forEach((el) => {

//将老数组的每一项都+1,然后push到新数组里

newArr.push(el+1)

})

//打印新数组

console.log(newArr)

//又或者,通过函数返回一个新数组

let newArr = (arr) => {

let res = []

arr.forEach((el) => {

res.push(el+1)

})

return res

}

console.log(newArr(arr))

这两种都是命令式编程,让引擎按照自己的意愿执行每一步,达到最终效果,命令式编程有一个最大的问题,就是所有的代码都是写死的,不可复用,假如那天产品经理拿着新需求过来了,他说:按照统计,用户不喜欢将数组的每一项都加1,而是每一项都加10,这个时候代码就复用不了,你必须去新建一段代码或函数,重新写一遍逻辑,实在是费时;

因此,到了这里,就不得不考虑如何提高效率了,基于此,也就有了函数式编程,其旨在尽可能的对函数复用,为了复用,就需要将函数拆解,使得函数的颗粒度达到最小,换句话说,就是一个函数只干一件事,绝不多干;

因此我们可以对上面的需求进行拆解:一个原数组,进行了一些操作,返回了一个新数组,具体如下

let arr = [1, 2, 3, 4];

let newArr = (arr,fn) => {

let res = [];

arr.forEach((el) => {

res.push(fn(el))

})

return res

}

let add_1 = el => el+1;

console.log(newArr(arr,add_1))

和上面的区别,将对数组的运算独立了出来,将运算方式作为参数传递进去,这样,如果需求变更成+10那么只需要新建一个+10的函数,比如

let arr = [1, 2, 3, 4];

let newArr = (arr,fn) => {

let res = [];

arr.forEach((el) => {

res.push(fn(el))

})

return res

}

let add_1 = el => el+1;

let add_10 = el => el+10;

console.log(newArr(arr,add_10))

甚至,同为加法运算,加的具体数字也可以作为参数传递进去

let add = (el,num) => el+num;

从例子可以看出,函数式编程就是将一个函数的执行过程,尽可能的细化,尽量写成是一系列函数的嵌套过程,这样,如果又其中一部分因为需求变更,那么只需要将变更的这部分函数重新设计编写,剩下的绝大部分逻辑都可以复用,以便达到提高效率(减少加班)的目的;

纯函数


如果函数的调用参数相同,则永远返回相同的结果,它不依赖于程序执行期间函数外部任何状态或数据的变化必须只依赖于其输入参数

简单的说,相同的输入,永远有相同的输出,为什么要这样?如果函数的执行结果取决于当前的外部变量结果,那么这种不可控不是一件很可怕的事情吗!

示例

let a = 10;

let add = b = b + a;

add(10); //20

a = 1;

add(10); //11

这就不是纯函数,因为明明执行了两次相同的代码,结果确实不一样的,试想一下,你执行了一个函数,结果是20,并用其结果进行了某些逻辑操作,之后再另外的地方又需要这个函数的结果作为参数了,待代码运行的时候,发现结果于预期不符合,这个时候要查错误,就比较麻烦了,因为没有报错,但结果就是不对;

因此为了保证程序的稳定性,应该尽可能的使用纯函数避免出现意料之外的情况,当然这种是相对的,具体情况还需要更具项目具体分析,只是说,能用纯函数的时候千万别弄别的幺蛾子;

高阶函数


顾名思义,高阶函数也是一种函数,与普通函数不同的是:高阶函数接收函数作为参数,或者返回的是一个函数;

let arr = [1, 2, 3, 4];

let newArr = (arr,fn) => {

let res = [];

arr.forEach((el) => {

res.push(fn(el))

})

return res

}

let add_1 = el => el+1;

console.log(newArr(arr,add_1))

函数式编程中的函数newArr,这个就是一个高阶函数,它接收了一个函数作为参数,因此它就是一个高阶函数,同样,闭包也是一个高阶函数,因为它把一个函数作为返回值返回出去,也符合高阶函数的描述;

JS中有很多内置的高阶函数,比如数组方法中的map,reduce等等,下面有几个示例

//使用reduce实现数组去重

let arr = [1, 2, 3, 4, 5, 6, 6, 7, 7, 7]

let newArr = arr.reduce((prev, cur) => {

prev.indexof(cur) === -1 && prev.push(cur);

},[])

//实现数组拍平

const arr = [1, [2], [3, [4, [5]]]];

//给Array扩展一个flat

Array.prototype.flat = function () {

let arr = function (curarr) {

return curarr.reduce((tol, cur)=>{

//判断当前元素是否是数组

//如果是数组,对其进行递归后再合并,如果不是数组,直接使用扩展预算父合并

return Array.isArray(cur) ? […tol, …arr(cur)] : […tol, cur]

},[])

}

return arr(this)

}

console.log(arr.flat())

递归


递归函数在项目中认为是比较常见的函数了,其过程就是执行的过程中调用自身,形成一层层函数嵌套;请直接看示例:

function count(num){

if(num <= 1){

return 1;

}

else {

return num * count(num-1)

}

}

console.log(count(4))

这是一个简单的递归函数,看下来之后发现,递归其实也是一个循环既然是循环那么必定存在终止循环的条件,比如上例的return 1,就是终止循环的条件,简单分析一下这个递归:

执行函数后:

  • 第一个阶段:发现参数num的值是4,不符合if条件,因此执行else语句,发现里面是一个return,但是执行到4* 后面的时候发现又是一个函数,因此4*这个表达式就暂缓,搁置了,得先执行函数count;

  • 第二个阶段:此时count的参数因执行num-1,就变成了3,但3仍然不符合if语句,还是得执行else,因此在执行else的时候3*这个表达式依旧被暂缓执行,还是得先执行函数count;

  • 第三个阶段:这次执行的count参数的值是2,发现不符合if,因此执行else,执行的时候2*被搁置暂缓了,依旧得先执行count;

  • 第四个阶段:这次count的参数值是1,符合if条件,因此返回1,到这里就没有嵌套函数了;

第四阶段执行完毕后,有了一个明确的返回值,不再有嵌套函数,因此就要开始执行前面被暂缓的表达式了,最终第三阶段后面的count函数的值是1,因此第三阶段的返回的表达式是2_1,第三阶段执行完毕返回,那么第二阶段的count函数也有了计算结果,最终就是3_2,第二阶段执行完毕,有了返回值,那么第一阶段的count的值就是6,因此执行的就是4*6,因此整个count执行的结果就是24

柯里化


是把接收多个参数的函数转成接收单一参数的函数,并且返回接收剩余参数的函数;

我的理解是,原本有一个函数,它能接收多个参数,现在将函数改成链式的调用,每次只接收一个参数,具体示例

//普通函数

function boy(name, age, single){

return 我是${name},今年${age}岁,我${single}单身

}

boy(“张三”, 18, “是”);

//柯里化之后

function boy(name){

return function (age){

return function (single){

return 我是${name},今年${age}岁,我${single}单身

}

}

}

boy(“张三”)(18)(“是”)

后面这个就是柯里化的函数,可以明确,这是一个高阶函数,因为返回的是函数,那么柯里化有什么优点呢?上面的例子好像没什么区别,实际上柯里化在现实开发项目中使用的是不大多,我本人使用的最多的是参数的固定,比如表单的验证上要确认每项输入不能有空格,具体请看示例

//正常函数

let macthInput = (reg,str) => reg.test(str);

macthInput(/\s+/g,“hello world”);

macthInput(/\s+/g,“helloworld”);

上例有一个验证函数matchInput,接收两个参数,第一个是一个正则,第二个是待检验的字符串,而注册表单等等表单上往往就十多项输入,每次检验都是需要输入正则,因此可以使用柯里化将正则参数固定下来,之后每次只需要输入待检验字符串就可以了

let macthInpuit = (reg) => {

return (str) => {

return reg.test(str)

}

}

let macthing = macthInpuit(/\s+/g);

macthing(“hello world”);

macthing(“helloworld”);

现在很多验证都已经内置在了UI框架中了,这种技巧平时使用的也不太多,不过思想得了解,当工作中用到多次使用同一个固定参数的时候,就可以考虑用柯里化的技术将参数固定下来;

防抖和节流


在前端中,resize,scroll,mousemove,mousehover等等事件,会不断的被触发,甚至一秒内会被触发几十几百次,如此高频的被触发,不仅造成计算机资源的浪费,还会降低程序的运行速度,造成浏览器卡死,奔溃;

防抖

当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时;

let deBounce = (fn,delay) => {

let timer = null;

//返回一个函数,…arg,将argument数组化

return (…arg) => {

//如果存在timer,就清除

if(timer){

clearTimeout(timer);

}

//到这里说明timer是null,所以就设定了一个延时,延时之后触发函数,并且将作用域this传递过去

timer = setTimeout(()=>{

fn(…arg)

},delay)

}

}

//因为返回的是函数,所以相当于onmousemove绑定的就是一个函数

oDiv.onmousemove = deBounce(changeNum,200)

总的来说,防抖就是通过setTimeout,设定了一个延时,判断在这个事件内是否是被不断的触发,如果不断的被触发,则不断的清空之前设定的延时,直到最后一次触发,那么在延时之后,执行这个函数,确保函数只执行一次;

节流

在持续触发事件时,保证一个时间段内只调用一次事件处理函数;

let throttle = (fn,delay) => {

let flag = true;

return (…arg) => {

if(!flag) return

flag = false;

setTimeout(()=>{

fn(…arg);

flag = true;

},delay)

}

}

总的来说,就是通过设置一个开关变量,来控制当前是否可以触发函数,之后通过setTimeout,来控制时间段内执行一次函数;

应用场景

简单的说个应用场景,就是用户输入账号和密码后自动登录账号,如果没有使用防抖函数,那么每次触发input事件都将向服务器发送请求,这样就会很浪费资源,并且不断的发送请求也会增加服务器负担,因此,通过使用防抖函数,只有当输入停止满一定时间后,才会想服务器发送一次请求,即使用户是中断输入思考账号密码,那请求的次数也是大大的减少了;

深拷贝和浅拷贝


深拷贝和浅拷贝,都是一种拷贝,是对原数据进行一次复制,因为在实际开发中,很多时候往往不能对原数据进行修改,需要将原数据进行复制保存,再对复制的数据进行筛选,操作;

在了解深拷贝,浅拷贝之前,首先得明确,原始数据类型和引用数据类型,原始数据类型存储在内存的栈中,引用数据类型存储在数据的堆中,原始数据的复制只需要赋值就可以了

let a = 1;

let b = a;

b = 2;

console.log(b,a) //2,1

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
得明确,原始数据类型和引用数据类型,原始数据类型存储在内存的栈中,引用数据类型存储在数据的堆中,原始数据的复制只需要赋值就可以了

let a = 1;

let b = a;

b = 2;

console.log(b,a) //2,1

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-5R1VkEp7-1715720052013)]

[外链图片转存中…(img-vqNlHvXb-1715720052014)]

[外链图片转存中…(img-JTxUkYJY-1715720052014)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值