以下总结个人多年的编程经验,分享一下关于js的整体理解,希望能够让初学js的道友、或者像我一样有很多年编程经验,但杂而不精的道友能有一些收获。
首先js最开始是有两个版本,一个是微软的vb版本,另外一个就是我们现在的js。vb不说了,没意义。
js核心是分为三块:
第一:ecmaScrpt,也是js核心,我们平常说的es5、es6中的es就是这个单词的缩写,5.6是它的版本,它发布的版本早已到10了,只不过大部分人可能都不知道。es规定了js编程规范,包括变量、语法、操作符、关键字、保留字等。
第二:dom,全称是document Object model 文档对象模型,dom也有自己的版本,从最开始的支持标签,到支持css、js、存储等等
第三:bom。浏览器对象模型
js是单线程处理机制,所谓线程可以理解为每一个计算机软件做每件事情都需要分配一个人去做,这个人名字就叫线程,这个线程有自己的办事风格,喜欢从从上到下一件事一件事的处理代码,如果遇到异步代码,线程会先将异步代码挂载(也就是放在一边),等到把非异步代码都执行完之后,在去处理异步代码。异步代码中也会被分成大(宏)任务、小(微)任务,线程会优先处理从小到大,优先处理微任务,再处理宏任务。
以上处理不停的循环就是很多装逼大佬口中的事件循环(event loop)
例如经常用的promise中的then方法属于微任务(promise本身属于同步,then属于异步),settimeout属于宏任务,即使settimeout被线程先挂载,也会优先执行then方法。
如果道友有任意一门后台语言基础的话,就知道OOP思想,面向对象思想也适用于在js中,因为js的底层开发语言就是C语言。
在js最初期是被称之为弱类型的语言,因为不管什么变量都是通过 var 关键字声明,不像后台语言一样 int 只能用来声明数字、string声明字符串。
到了es6版本,更新出两个新的声明关键字:let、const。之所以新增两种声明类型是因为随着js版本迭代,支持的拓展越多,前端代码也出现了类似后台语言一样的问题,性能和效率的问题。
而var作为弱类型声明类型。var 的作用域(也就是当前代码在当前环境下,或者说上下文中,能够正常执行的区域)是全局的也可以说是整个函数的,当我们用 var 声明一个变量,会发生一件事情,就是系统会让电脑在内存中开辟一块空白的空间存放这个变量,可能放在堆、或者栈 上。
变量声明好之后,就会等待调用,如果声明了很多变量,在这些变量被使用之前,就会有很多内存被提前不必要的占据,影响访问的速度和性能。这时,使用let 或者 const 会或多或少的减少不必要的内存消耗,因为let /const 属于块级别作用域,只在当前块内有效,当此块区域内的代码被执行到的时候声明 let,const,执行完毕就会触发前端垃圾回收机制回收掉。
当然即便如此,还是可以通过 闭包(函数嵌套,函数被当做参数或者当做返回值) 访问到,此时,变量如果被循环调用,就会跳出垃圾回收机制(垃圾回收是自动触发,分为标记、计数两种,标记true就不会被回收,计数>0也不会被回收,因为代表被占用)。如果该变量是一个需要开辟内存空间的类型,就会循环不停的开辟新内存空间,此时就又会发生装逼大佬口中的另一个称之为“内存泄漏”问题。
解决办法两种:1.少用闭包。2.函数使用完手动清理变量,置为空。
其实关于性能问题,当前硬件发展速度远远超过软件发展速度,除了少数的游戏或cad对电脑还有要求之外,常见的软件哪个不能再现在的电脑上流畅的运行起来?不能运行的电脑谁还花钱买?如果有人买了配置很垃圾的电脑,你认为他会使用或者说有能力使用你们公司这种还要求性能方面的软件吗?
以上基本上对js就做了综合的概括,当然,在我们实际应用之中,基本都用不到,能用到的在下面:
上面提到OOP面向对象思想对于js也适用,现在说一下js的oop,oop三大特性:封装、继承、多态。js也是基于函数和对象的语言,在es6的class之前,相信大部分人都自己对函数进行过构造(也就是封装),模拟一个es6中的class。
构造函数里面包括了属性、方法。然后通过new 关键字使用,其实在后台语言中,new关键字是起到实例化一个类的作用,让我们可以正常使用自己封装的对象函数,就是一个语法而已,这个语法会让系统为你在内存堆里开辟一块新的空间。在js中,被装逼大佬提到最多的莫过于:原型链,有人看到这里会问。为啥面向对象讲的好好的突然跳到原型链上,其实,js中的原型链是基于原型对象,也就是对象,js中每一个函数都有自己的原型对象prototype,也就是普通对象property,因为不管原型对象还是普通最后都是继承自object。每个原型对象中又有对应的指针将该对象指向构造函数,当我们用new 关键字实例化一个构造函数时,也会有一个特定的指针指向构造函数,这样我们就可以通过实例化的变量类型来获取函数中的属性和方法。这么看起来有点绕,我尝试用个简单的方法:
new 通过内部指针指向 构造函数
构造函数包含:原型对象。
原型对象包含:函数指针 指向构造函数
因此:new的其实就是一个新的原型对象。开辟了一个全新的内存空间。
如果说在我们构造函数中的某个属性的值指向了另外一个被我们new好的构造函数实例,相应的指针就会指向该实例的引用,如果继续在该实例中还有属性指向另外一个实例,这样层层嵌套下去,就是原型链。
说道这,不得不提到深拷贝和浅拷贝的问题,顾名思义,拷贝就是a复制b,浅拷贝就是a单纯的复制了b,复制了b的引用,要变一起变,是多对一的关系, 深拷贝就是a不但复制了b,和b的引用,而且还自己开辟了一个全新的空间,存放,你变你的,我变我的,是多对多的关系。
这里在说一下栈和堆,都是存放数据的地方,栈的空间是系统自动分配,堆是需要人为分配(比如new一个),栈是先进后出。