0、前言
本人在大学时非常痴迷java,认为java就是世界上最好的语言,偶尔在项目中会用到一些javascript,但基本没放在眼里。较全面的接触javascript是在实习的时候,通过这次的了解发现,javascript其实是一门非常优秀而且有意思的语言,从那时开始喜欢上了这门语言。
javascript里面很多的概念和目前主流的语言是非常不一样的,它更多的是继承了函数式编程语言的特点。虽然语言的第一版的设计据说只花了七天时间,但其血统还是挺好的。个人认为javascript之所以会经久不衰并且应用越来越广泛,和其血统是离不开的,它可能刚好触及到某些“本质”的东西。当然不是要鼓吹“javascript是世界上最好的语言”,javascript语言本身是有许多瑕疵的,这也是被许多人诟病的地方。它优秀的地方设计得非常好用,能够满足绝大多数的使用场景,一些小瑕疵只要稍加注意,并不会产生太大的影响。
对于有一定编程经验的人来说,第一次接触javascript一定会觉得非常简单,会有似曾相识的感觉,但如果轻视它,不去理解它的核心特点,一定会受到惩罚,让你一脸懵逼。本文就整理了几个核心的需要理解的知识点,希望能够帮助有编程基础的初学者快速理解javascript。
1、数据类型
javascript数据类型设计得非常简单,总共只有6中,分别是boolean、string、number、undefined、object,function。一般将能够通过typeof运算符区分的归为一种数据类型。其中object,function为引用类型,剩下都为基本类型。javascript的数据类型虽然简单,但有几个地方需要特别注意一下。
number:
number类型底层采用双精度浮点数存储,能够表示整数,也能表示小数。由于这个原因,number能够安全表示的整数范围是-9007199254740991 ~ 9007199254740991,超出则可能丢失精度。还比如0.1 + 0.2,其结果为:0.30000000000000004。number类型有个特殊值NaN,表示“不是个数字”,比如1/'a'就会返回NaN。
undefined:
这是一种非常特殊的数据类型,表示未赋值。变量或属性定义后未赋值就为undefined。这种数据类型用处不多,一般只是拿来区分有无赋值。
object:
object类型涵盖的东西非常广,Array,RegExp这些内置类型的对象也是object类型。null是一个非常特殊的对象,表示空对象,和undefined很像,只不过null是对象类型。
function
函数虽然被当做一种数据类型,但其本质还是一种特殊的对象,是从Object继承而来的。
2、函数及其运行时
个人认为,函数是javascript里面设计最巧妙的一个地方,许多有意思的特性都是围绕函数展开的,比如闭包和继承等。javascript里的函数还是“一等公民”,能够作为参数传递、返回值返回,使用上极其灵活。
理解函数的执行首先要理解作用域的概念。在ES6之前,javascript里只有全局和函数作用域两种作用域,到ES6才引入了块级作用域。
函数在执行时会创建一个执行环境,push到执行栈的顶端,执行环境中就包含了当前函数的作用域。作用域包含了当前函数的局部变量以及内置的变量比如this,arguments。函数作用域是在运行时确定的,非运行时讨论作用域是没有意义的。作用域是如何形成的?函数在定义时会有个[[scope]]的内部属性指向当时的作用域,一旦函数开始执行,它会创建一个新的作用域,并且用指针指向函数[[scope]]属性指向的内容,如此就形成了一条作用域链,作用域链的最外层是全局作用域。
作用域链的作用就是做标示符解析。函数在执行时遇到变量标示符解析的时,首先从当前作用域查找,找不到则一直沿着作用域链向上搜索。作用域链会形成一个非常有意思的特性,叫闭包。就是外层函数执行完了(栈帧已经弹出),如果内层函数对象还没被回收,外层作用域的变量环境会一直保留着,当内层函数执行的时候,依然能访问外层作用域的变量环境。
3、面向对象
javascript是一门非常面向对象的语言,对象的创建和使用极其方便。面向对象的4个特性里面,封装性的体现略差一点,由于javascript是动态语言,讨论多态性意义也不是很大。个人认为面向对象主要是解决了两大问题,一是以一个符合现实世界观的方式组织抽象的代码,二是解决复用的问题。前者先不做多讨论,那是一种思维方式和语言关系不大。后者在javascript里的实现是非常有意思的。
可以通过两个方法来解释对象的复用。假设有a、b两个对象,b对象希望复用a对象的属性和方法。一种做法是将a对象中所有的内容拷贝到b对象,这是Object.assign方法的功能。这种做法虽然很朴实,但是在一些简单的复用场景是非常有用的。改方法的问题是会造成数据的冗余,关系复杂的时候维护起来就很困难。另一种做法是在b对象中保留一个指向a对象的指针。当我在访问b对象的内容时,如果当前对象查不到,则沿着该指针继续搜索下去。这是Object.create方法能达到如此效果。Object.create本质是利用了javascript原型链的特性。如何构造原型链?必须利用函数的另外一个角色——构造器。当把new 操作符用到函数之上时,函数就发挥着构造器的角色。通过构造器构造对象时,其首先会创建一个空对象,然后将对象的原型指向构造器的prototype属性,以此就可以形成原型链了。javascript里本质是没有类的概念,Es6引入的class关键字只是个语法糖,本质还是通过原型链实现继承。
个人感觉javascript里的继承更直接了当,直接从对象的角度去考虑复用,其实是更接近现实的世界观。
4、事件循环
javascript还有个明显的特点——单线程。javascript早期都是用作ui开发,单线程可以比较好当规避同步问题,降低了ui开发门槛。单线程需要解决的是效率问题,javascript里的解决思想是异步非阻塞。就是javascript主进程不要做耗时的事情,保证能快速执行快速相应,而将耗时的、io等待的事情交给其它进程去做。
在实现上javascript利用了一个事件循环和任务队列,事件循环会不断的检查任务队列有没有需要处理的任务,有则马上取出处理,无则等待。进入一个调用栈时,对于io操作一般是要立即返回,而把后续要做的事情放到任务队列里,这样就不会造成io的阻塞。
正是单线程的特性,让javascript在后端开发领域也火了一把。nodeJs让javascript可以跑在服务端后,对并发的支持有自己独特的优势。
5、后记
近些年javascript的应用越来越广泛,“任何能够用JavaScript实现的应用系统,最终都必将用JavaScript实现”的预言好像正在一步步变成现实。个人觉得ES6、ES7、ES8将这门语言变复杂和臃肿了。ES3其实就将最重要的基石确定了。javascript有这么强大的生命力,个人觉得主要原因是其简洁而强大,希望后面不要变得太臃肿。