现今各种框架、工具‘横行’,到处在讲原理和源码,更有跨端技术需要我们去探索,但如果基本功不好,学什么都是事倍功半,效果很不好,花费时间的同时打击自信心。此篇文章,为我所计划的【轻聊前端】系列第三篇,旨在系统地、逻辑性地把原生JavaScript知识分享给大家,帮助各位较为轻松地理清知识体系,更好地理解和记忆,我尽力而为,望不负期待。
前端er们经常看到这么一句话:“JavaScript的一切皆为对象”。
也有说“JavaScript是一门面向对象”的编程语言,还有说“不对,JavaScript不具备正统面向对象的特征,应该说是基于对象。”
谁是谁非?
我们“不是”对象
上篇文章我们讨论了JavaScript的数据类型,列出了这么几种:
Number、String、Null、Undefined、Boolean、Symbol
称为基本类型,它们有自己的类型,不可再分。它们是对象吗?
回答这个问题,得先看两个小问题:
- 对象长什么样
- 对象有什么特点
长什么样
第一篇文章里聊编程的时候,我们就定义过对象,长这样:
1 | let people = { |
有什么
它有什么呢?
属性:性别、年龄、职业
方法/能力:吃饭
由此看,基本类型并不是对象,既没有对象的外表,也没有对象的特质。
从“值”到“对象”
我们说过,基本类型存储的是值本身,但如果不论什么时候,值都只是值,那么这门语言等于废了一半的武功。
所以,当需要的时候,基本类型的值就会被转化成对应的对象,又称“包装类型”。简单理解,就是被包装成了对象。
就算不懂编程,看过代码的人应该对几个东西眼熟,比如:toString()、parseInt()/parseFloat()等。
如果你到代码里找它们的定义,发现并找不到,哪儿来的呢?
就来源于“包装类型”。
数字、字符串和布尔值对应的包装类型就是“Number、String、Boolean”。
像这样一段代码
1 | let a = 1; |
当运行的时候,会在内部经历三个过程:
- 创建Number类型的实例
- 调用实例方法toString()
- 销毁实例
创建实例的过程可理解为执行了这么一行代码:
1 | let a = new Number(1); |
使其具备了和对象一样的可访问属性和可调用方法。
值得注意的是第三步也很关键,即销毁实例,虽然在以上代码执行的当时,变量具备了调用方法或者访问属性的能力,但是,在执行完毕之后实例会被立即销毁,恢复它本来“身份”,这就使得你想再给它添加属性或者方法是不会生效的。
1 | let a = 1; |
String 和 Boolean 原理同 Number,不再赘述。
除了以上三者,JavaScript中还有几种基本的引用类型,这里一并介绍:
Date(日期)
Date是有很多使用场景的类型,比如:生日、文章发布时间、活动截止时间等等,都要用到日期。
这个类型不需要开发者自己定义,可以直接使用,可以拿到毫秒数,也可以拿到具体的年份,月份,星期,时/分/秒等,十分灵活,开发者按照需要进行格式的转换和组合就好。
RegExp
正则,一般以正则表达式的形式存在,而正则表达式通常被用来检索、替换符合某个规则的文本**。
比如我们最常见的,邮箱、电话号码、身份证号,当用户输入一个有格式要求的信息时,在前端交互上就要对数据进行校验,如果格式不符,就提示用户输入正确格式的内容,如果在提交之后再发现不对,体验就很不好了。
示例:1
2^[0-9]*$ //匹配数字
^[\u4e00-\u9fa5]{0,}$ //匹配汉字
正则表达式有很多符号的属性和规则,能够发挥的作用十分强大,这里不再赘述,读者可以找专门的资料进一步了解。
集合引用类型
介绍完基本引用类型,再看集合引用类型。
Array
数组用来存储一系列同类型的数据,当然,通常我们是这么做的,但JavaScript中的常规数组不受类型限制,允许存储任意类型的数据。
为什么说数组是对象呢?它和上面提到的一样,具有包装类型 Array。
当我们对其进行访问时:
1 | let a = [1,2,3]; |
均会调起包装类所具备的属性和方法来为我们服务。
Object
按理说,讲对象应该第一个提到Object,为什么现在才提:
一、它就叫对象。
二、它是用来自定义的对象,本身没有固定的属性和方法,开发者定义了才有。
基于以上两点,它是最好理解的对象类型,就放在后面,当然,关于对象,还会有另一篇文章详细介绍。
除了以上二者,JavaScript中新增了几种集合引用类型。
Map
ES6之前,存储键值对都是用Object,Map是一种新的类型,它在多数场景下和 Object 表现出的特性一致,但仍存在差异。
比如:
1 | let a = new Map(); |
还有其他方法不再赘述,但Map和Object还有两点明显不同:
一是键的类型不再有限制,可以是任意类型。
二是它会维护插入的键的顺序。
PS:其他差异及如何选择使用,后续文章再详聊。
Set
Set类型也是ES6后新增的,它跟Map很像,大多数方法和API也是共有的。
但它不是专门用来存储键值对,而是存储值,可以存储任何类型的值。
示例:
1 | let a = new Set([1,2,3]); //初始化 |
Set使用add()方法添加值,Set同样会维护值的插入顺序且不允许值有重复,这一点给开发过程中的一些场景提供了很大便利,比如常见的数组去重,往常我们需要专门写个方法处理,现在只需要将数组使用Set方法处理一次就好。
PS:Set其他内容同上略过。
Function
聊完以上几种类型,就是函数了,函数也是对象?虽然我们很少像常规对象那样来用,但它也是对象。
它具备哪些属性和方法呢?
每个函数都至少有两个属性:length和prototype,length表示函数命名参数的个数,prototype则并非函数独有,以上所提及的其他类型的对象也都有,它是保存引用类型所有实例方法的地方,譬如:toString()、valueOf()等。
函数还有三个很有用的方法:call()、apply()、bind(),这三个方法用于将函数调用时的this 指向传给它们的对象。比如:
1 | var color = "red;" |
如果没有call,输出会是 “red”,现在是 “blue”。
除此之外,函数曾经有过一种重要用途——构造函数。
1 | function Person(name, age, job){ |
在ES以前的版本当中,这曾是很流行也很经典的一种定义“类”和创建“实例”的模式。
这里的函数和普通函数没什么区别,区别就在于使用了new关键字,从而触发了内部的一些特殊处理,这里点到为止~
其他内置对象
Math
除了Date对象,Math对象是另一个非常有用的内置对象,有很多实用的方法,比如:
- 求最大、最小值——max()/min()
- 四舍五入、上下取整——round()/ceil()/floor()
- 随机数——random()
- 绝对值——abs()
…
这些方法为数字处理提供了极大便利。
Window
有些对象在开发时并不会显式调用,有些属性和方法也不属于以上提到的任何一种对象,比如:全局定义的变量和函数,内置方法parseInt()和parseFloat()等。window作为浏览器中全局对象的代理对象就担当了这么一个角色,它们自动归属于window。
同时,很多浏览器API及相关构造函数都以window对象属性的形式暴露出来,以便开发者使用。
常见的有窗口宽高、像素比、滚动、打开窗口等,都是window对象的范畴。
window对象并不孤独,它属于BOM(Browser Object Model)浏览器对象模型这个大的类当中,BOM是很丰富的,这里简单介绍一下其他包含在BOM中的对象:
location对象:可获取所访问的URL的各种信息(协议、域名、端口号等)。
navigator对象:客户端信息(名称、网络、电池、地理位置、语言、多媒体设备等)。
screen对象:屏幕宽高、屏幕朝向等。
history对象:当前窗口用户的导航历史记录。
总结
行文至此,可以告一段落。
为什么说完变量就说对象?这和很多文章的逻辑都不一样,本文并没有意图说清楚所有对象,只是帮助大家理清这么个关系。
你也看到了,就算是基本类型,它们也有对象的表现,在使用过程中会触发对象的行为(访问属性、调用方法),另外有若干内置对象可供使用,如果没有“它们也是对象”的概念,后面很多东西就是说不通的,难免有困惑。
但这就是“一切”了吗?
JavaScript程序的整体是由ES、BOM、DOM文档对象模型(Document Object Model)组成,上文只提了ES和BOM,DOM同样也是对象,不仅如此,跟DOM紧密相关的“事件”也是,包括我们在业务当中会接触到的File,前后端请求用到的XMLHttpRequest等等,它们都是对象。
那么,回到文章开头,说JavaScript是基于对象的编程语言是相对准确的,它整个运行在由多种对象构成的系统里,建立了这样的认识之后,再去看JavaScript的程序,就清晰很多,因为大多的程序都只是在干一件事儿:
创建某个对象的实例,访问或者改变属性,调用方法。
好了,我们下篇文见~