当前位置: 代码网 > it编程>编程语言>Javascript > 2024年前端最全《JavaScript》JavaScript进阶知识点(一),Kotlin可能带来的一个深坑

2024年前端最全《JavaScript》JavaScript进阶知识点(一),Kotlin可能带来的一个深坑

2024年07月28日 Javascript 我要评论
三套“算法宝典”开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】算法刷题LeetCode中文版(为例)人与人存在很大的不同,我们都拥有各自的目标,在一线城市漂泊的我偶尔也会羡慕在老家踏踏实实开开心心养老的人,但是我深刻知道自己想要的是一年比一年有进步。最后,我想说的是,无论你现在什么年龄,位于什么城市,拥有什么背景或学历,跟你比较的人永远都是你自己,所以明年的你看看与今年的你是否有差距,不想做咸鱼的人,只能用尽全力去跳跃。祝愿,明年的你会更好!

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

html5

var count = function(num){

console.log(num);

}

count(1);

这样就去掉了a,之后发现好像还可以简写,可以将count的定义直接去掉,直接改成调用,如下

function(num){

console.log(num);

}(1) //报错

这么写之后发现,js不识别这种写法,因为不管在任何时候,关键字function都会被识别成函数定义的开始,因此不能这么写;

然而发现,函数表达式的后面则可以根括号,到这一步的关键就是要将函数声明转成函数表达式,那么如果转换呢,其实也简单,就是加一对括号

(function(num){

console.log(num);

})(1)

这就是一个立即执行函数,定义了一个匿名函数,并且立即执行了,外界的变量可以通过参数传递进入,比如需要将window对象传递进去,那么可以

//这个window就是全局环境window

(function(window){

console.log(window);

})(window)

闭包


什么是闭包?《高程》一书中的解释是:指有权访问另外一个函数作用域中的变量函数,这里有几个关键词,通俗点讲,就是首先闭包是一个函数,其次这个函数存在于另外一个函数体内,之后函数体内的函数访问了不属于自己用用于内的变量,可能有点绕口,示例如下

function func(){

var number = 1;

function printnum(){

console.log(number);

}

}

这就是一个闭包,虽然没有什么用,因为这么写并没有什么实际用处,通常闭包的使用是将函数体内的函数作为返回值返回出去,也就是这样:

function func(){

var number = 1;

return function (){

console.log(number);

}

}

为什么要这么做?其中最大的目的就是为了解决一个问题,为了保留执行结果且不污染全局环境,在了解这个结论之前,首先得明白,js因为其垃圾回收机制,会导致变量只要离开环境,其值就会被释放,也就是会被销毁,为了不被销毁,只有想办法不让它释放,这么写可能有点空洞,看个例子,比如:

现在项目中有一个函数,每次执行过后要保存执行的结果,之后根据当前结果判断按哪种方式继续执行,具体的话就是有如下例,函数每次执行的时候都需要在上次的执行结果上+1,之后返回

function count(){/代码/}

count() //1

count() //2

count() //3

function count(){

var i = 0;

i++;

return i;

}

count() //1

count() //1

这个函数执行肯定不行,因为每次count执行的时候,都会重新定义变量i,之后i++,再之后返回i,执行结束以后,count函数就会被释放(销毁)掉,这也就导致了每次count的执行结果都是1;

考虑到函数每次执行会重新定义i,那么只有将变量i提出到函数体外,i的值才能保留,比如:

var i = 0;

function count(){

i++;

return i;

}

count() //1

count() //2

能实现要求,但是又带来了一个新问题,变量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,到这里就没有嵌套函数了;

总结

三套“算法宝典”

28天读完349页,这份阿里面试通关手册,助我闯进字节跳动

算法刷题leetcode中文版(为例)

人与人存在很大的不同,我们都拥有各自的目标,在一线城市漂泊的我偶尔也会羡慕在老家踏踏实实开开心心养老的人,但是我深刻知道自己想要的是一年比一年有进步。

最后,我想说的是,无论你现在什么年龄,位于什么城市,拥有什么背景或学历,跟你比较的人永远都是你自己,所以明年的你看看与今年的你是否有差距,不想做咸鱼的人,只能用尽全力去跳跃。祝愿,明年的你会更好!

由于篇幅有限,下篇的面试技术攻克篇只能够展示出部分的面试题,详细完整版以及答案解析,有需要的可以关注

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com