Webpack 打包优化之体积篇

原文出处: 晚晴幽草轩

 

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如Vue, React, Angular等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者Browserify, Grunt,到后来赢得宝座的 Gulp, 以及独树一帜的 fis3, 以及下一代打包神器 Rollup ;在 browserify,grunt,gulp,rollup,webpack 可以一窥其中部分对比。在本文要探究的是,当前打包工具绝对霸者 Webpack。

webpack

Webpack,当前各大主流框架默认配备的打包方案,对其如何使用,已有较完备中英文文档;并且,各主流框架也有对应 CLI 予以基础配置,故不作为探讨范畴。从产品层来讲,如何使得构建的包体积小、运行快,这有必要不断摸索实践,提炼升级,使之臻于最佳。本文将从以下些许方面,对 Webpack 打包体积方面,做下优化探讨(备注: Webpack实践版本: 3.3.0):

定位 webpack 大的原因

这里推荐使用 webpack-bundle-analyzer —— Webpack 插件和 CLI 实用程序,她可以将内容束展示为方便交互的直观树状图,让你明白你所构建包中真正引入的内容;我们可以借助她,发现它大体有哪些模块组成,找到不合时宜的存在,然后优化它。我们可以在 项目的 package.json 文件中注入如下命令,以方便运行她(npm run analyz),默认会打开 http://127.0.0.1:8888 作为展示。

“analyz”: “NODE_ENV=production npm_config_report=true npm run build”

93f72404-b338-11e6-92d4-9a365550a701

 

当然,同类型的还有 webpack-chart 以及 webpack-analyse,这两个站点也是以可视方式呈现构造的组件,可以让你清楚的看到模块的组成部分;不过稍显麻烦的是,你需要运行以下命令,生成工具分析所需要的 json 文件:

JavaScript

1
2
3
webpack –profile –json > stats.json
// 如果,运行指定的 weboack 文件,可用此命令
webpack –config build/webpack.prod.conf.js  –profile –json > stats.json

引入 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 提供了以大幅度提高构建时间性能的方式拆分软件包的方法。其中原理是,将特定的第三方NPM包模块提前构建👌,然后通过页面引入。这不仅能够使得 vendor 文件可以大幅度减小,同时,也极大的提高了构件速度。鉴于篇幅,具体用法可参见:webpack.dll.conf.js

外部引入模块(CDN)

如今前端开发,自然是使用ES6甚至更高版本,撸将起来才更嗨。但由于浏览器兼容问题,仍得使用 babel 转换。而这 babel-polyfill 也得引入以确保兼容;还比如项目开发中常用到的 moment, lodash等,都是挺大的存在,如果必须引入的话,即考虑外部引入之,再借助 externals 予以指定, webpack可以处理使之不参与打包,而依旧可以在代码中通过CMD、AMD或者window/global全局的方式访问。

JavaScript

1
2
3
4
5
6
7
8
9
// webpack 中予以指定
externals: {
  // ‘vue’: ‘Vue’,
  // ‘lodash’: ‘_’,
  ‘babel-polyfill’: ‘window’
}
//
<script src=”//cdn.bootcss.com/autotrack/2.4.1/autotrack.js”></script>
<script src=”//cdn.bootcss.com/babel-polyfill/7.0.0-alpha.15/polyfill.min.js”></script>

需要补充的是 externals 中:key 是 require 的包名,value 是全局的变量。

让每个第三包“引有所值”

确定引入的必要性

前端发展到如今时期,倘若项目采用了 MVVM模式框架,数据双向绑定,那么像 jQuery 这般类库,不能说没有丝毫引入的必要,至少可以说确实没有引入的必要。对此,如果还有些顾虑,完全可以参考下 YOU MIGHT NOT NEED JQUERY;用原生写几行代码就可以解决的事儿,实在不易引入这么个庞然大物,平添烦恼。

避免类库引而不用

倘若这类情况发生,对整个打包体积,不仅大而且亏。项目一旦大了,很难人为保证每个引入的类库,都被有用到,尤其是二次开发。所以工具的利用十分必要,强烈推荐类如 Eslint这般工具,并且注入对应规则,对声明却未使用的代码,给予强制提醒;这不仅可以有效的规避类似情形发生(也适用于普通变量的检测),而且还能使得团队代码风格,尽可能地保持相似;要知道代码足够遵守规则,也可让压缩工具更有效压缩代码,一举多得,何乐不为?

尽量使用模块化引入

如果说 jQuery 确实没有引入必要,很多人会同意;但对于 lodash 这类依赖的工具,并不是所有人都会去造一发轮子的。然而全包引入 400kb 的体量,可否有让你心肝一颤?幸好的是,lodash 提供了模块化的引入方式;可按需引入,快哉快哉:

JavaScript

1
2
3
4
5
import { debounce } from ‘lodash’
import { throttle } from ‘lodash’
// 改成如下写法
import debounce from ‘lodash/debounce’
import throttle from ‘lodash/throttle’

擅懒如你的优秀程序员,是否也发现这样写颇为麻烦?那么恭喜你,这个问题已经被解决;lodash-webpack-pluginbabel-plugin-lodash 的存在(组合使用),即是解决这问题的。它可将全路径引用的 lodash, 自动转变为模块化按使用引入(如下例示);并且所需配置也十分简单,就不在此赘述(温馨提示:当涉及些特殊方法时,尚需些留意)。

JavaScript

1
2
3
4
// 引入组件,自动转换
import _ from ‘lodash’
_.debounce()
_.throttle()

额外补充的是,即便采用如上写法,还是不够快捷,每个用到的文件,都写一遍 import,实在多有不便。更可取的是,将项目所需的方法,统一引入,按需添加,组建出本地 lodash 类库,然后 export 给框架层(比如 Vue.prototype),以便全局使用;详情可参见:vue-modular-import-lodash

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// helper 文件夹下 lodash,统一引入你需要的方法
import _ from ‘lodash’
export default {
  cloneDeep: _.cloneDeep,
  debounce: _.debounce,
  throttle: _.throttle,
  size: _.size,
  pick: _.pick,
  isEmpty: _.isEmpty
}
// 注入到全局
import _ from ‘@helper/lodash.js’
Vue.prototype.$_ = _
// vue 组件内运用
this.$_.debounce()

尽可能引入更合适的包

作为前端开发的你,想必知道有 momentjs 的存在(Parse, validate, manipulate, and display dates in javascript.);更多的是,你想必知道它很好用,然而它的体态却十分丰满(丰盈),没念及此,是否有重新造轮子的冲动?SpaceTime: A lightweight way to manipulate, traverse, compare, and format dates and times across planet Earth。 具有与 monent 相似 api 的新类库,其体积又相对小很多(当然,据观察其灵活度略逊一筹);date-fns:现代JavaScript日期实用程序库( Modern JavaScript date utility library ),如 lodash 一样,可支持模块化;知道这些或者更多的你,会如何选择?

按需异步加载模块

关于前端开发优化,重要的一条是,尽可能合并请求及资源,如常用的请求数据合并,压缩合并 js,构造雪碧图诸此等等(当然得适当,注意体积,过大不宜);但,同时也当因需制宜,根据需要去异步加载,避免无端就引入早成的浪费。webpack 也是内置对这方面的支持; 假如,你使用的是 Vue,将一个组件(以及其所有依赖)改为异步加载,所需要的只是把:

JavaScript

1
import Foo from ‘./Foo.vue’

改为如下写法:

JavaScript

1
const Foo = () => import(‘./Foo.vue’)

如此分割之时,该组件所依赖的其他组件或其他模块,都会自动被分割进对应的 chunk 里,实现异步加载,当然也支持把组件按组分块,将同组中组件,打包在同个异步 chunk 中。如此能够非常有效的抑制 Javascript 包过大,同时也使得资源的利用更加合理化。

生产环境,压缩混淆并移除console

现代化中等规模以上的开发中,区分开发环境、测试环境和生产环境,并根据需要予以区别对待,已然成为行业共识;可能的话,还会有预发布环境。对待生产环境,压缩混淆可以很有效的减小包的体积;同时,如果能够移除使用比较频繁的 console,而不是简单的替换为空方法,也是精彩的一笔小优化。如果使用 UglifyJsPlugin 插件来压缩代码,加入如下配置,即可移除掉代码中的 console:

JavaScript

1
2
3
4
5
6
7
8
new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false,
    drop_console: true,
    pure_funcs: [‘console.log’]
  },
  sourceMap: false
})

Webpack3 新功能: Scope Hoisting

截止目前(17-08-06), Webpack 最新版本是 3.4.1;Webpack在 3.0 版本,提供了一个新的功能:Scope Hoisting,又译作“作用域提升”。只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快:

JavaScript

1
2
3
4
5
module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

据悉这个 Scope Hoisting 与 Tree Shaking,最初都是由 Rollup 实现的。在个人中实践中,这个功能的注入,对打包体积虽有影响,却不甚明显,有兴趣的盆友可以试下;更对关于此功能讯息,可参见 Webpack 3 的新功能:Scope Hoisting

本文自本月(08)四号开始陆陆续续写,原本的内容意图是,涉及 Webpack 打包优化的体积和速度两个方面;岂料,临近写完的时候(06号晚),已记不得多久没关机的 Mac,竟然被重启了下;屋漏多半会偏逢连夜雨,那一向会自动同步保存(30min)的作业部落,竟然没给同步,WTF!整个周末的敲敲打打,皆付之东流,泪崩泪目😂。无奈之下,只得重新写过,直到夜深,才补齐关于体积优化那部分;毕竟涉及内容较多,干脆,就分成两部分来完成😪。也在此提醒广大笔友,及时备份数据并确认,这很重要😂。在此也预告下一篇 《Webpack 打包优化之速度篇》,当然,此文也扔在完善中。

提高网页可访问性的十条建议

这篇文章提供了十条有关于提高网页可访问性的指导原则,这些原则将保证提高你网站的可访问性。

引用万维网(W3C)的创始人@Tim Berners-Lee一句话来说网络的力量是它的普遍性。作为靠制作网站谋生的人,我们有责任确保每个人都能更好接触到他们。网页可访问性似乎是一项艰巨的任务,但它确实比听起来要容易很多。

这十条网页可访问性准则旨在确保所有网站都是通用的。

这不仅能帮助屏幕阅读器用户,而且还能改善浏览体验,以减缓连接速度。我们已经按照实施时间对我们的指导方针进行了排序,让您清楚地了解在这个过程中需要付出多大的努力。在你不知所措之前,请相信我的话,这是值得的。

什么是网页可访问性?

根据W3C的说法,Web可访问性意味着每个人都可以感知、理解、导航、与Web交互,并为Web做出贡献。在这方面,网站的可访问性包括所有影响网站访问的条件,包括视觉、听觉、物理、语言、认知和神经功能障碍。

你会在网上找到很多关于这个话题的内容,如果这个话题让你感兴趣的话,你应该更深入地了解网站可访问性倡议WAI)。

考虑到这一点,以下是我们提出的十条提高网站可访问性的建议。

不要依靠颜色

颜色是一种强大的工具,我们经常用来表达情感和在网上交流信息。然而,我们不应该把所有的信息都用颜色来传达,用来表达我们的用户的意思和信息。

为什么?

例如,人们普遍认为绿色意味着是正确,红色意味着错误,但是当我们把它作为我们唯一的沟通方式时,会发生什么呢?

web-accessibility-color-blindness-1

色盲是最常见的视力缺陷之一。全球人口总数大约有

4.5%

的人有这方面的缺陷(这个用户量已经超过了IE11用户数量)。

如果我们用户界面中显示的重要信息只使用颜色来传达,那么也意味着全球将有4.5%的人受影响。

颜色应该只是错误或确认信息的补充,但不能成为我们使用的唯一工具。为了确保我们的重要信息能够触达到所有的用户,我们应该要在表单中添加标签或图标来填充正确的信息。

web-accessibility-color-blindness-2

Caniuse.com提供了一个非常有趣的解决方案,它提供了一个替代颜色调色板的兼容列表:

web-accessibility-caniuse-1

在设计时检查颜色的识别力和对比是比较理想的,所以要确保你和你的设计团队有正确的工具。我们强烈推荐使用Sketch的Stark插件,可以帮助你设计可访问性!

不要阻止放大

在响应式设计的时代,我们可能犯了一些不负责任的错误。

其中

maximun-scale=1.0

就是幽灵之一,它让移动设备的网页无法放大。

在欧洲和亚洲,散光影响着30%~60%的成年人,但模糊的视觉会影响到所有年龄和民族的人。

放大的能力不仅仅是一个WCAG指导方针,而是日常生活中一个简化的工具。所以下次你在建立一个响应式网站的时候,要考虑到视力模糊的人群,比如我们的妈妈。

除了让用户可以自由地在移动设备上缩放之外,还要记得检查PC端浏览器上高达200%的放大特性。

重新重视

alt

属性

不管你制作网站多久,你可能会惊讶地知道下面这些关于

alt

属性的建议:

  • alt

    属性是

    img

    元素的一个强制性属性,但是

    alt

    的属性值是空的话完全有效。如果图像是装饰的或者没有必要的阐述页面的内容,那么你可以简单地使用

    alt=""
  • 屏幕阅读器会告诉用户
    img

    是一个图像,其中

    alt

    的值告诉用户这个图片表达的内容

  • 图像的功能和它的含义一样的重要,如果你的网站Logo链接到你的网站的主页,那么img
     

    alt

    的属性值应该是“首页”,而不是“Logo”

  • 图像替换文本不仅仅是关于可访问性的。有时候,对于网络慢的用户为了提高浏览器的检验(更快)会禁用网页的图像。对于这些用户群体,你就需要记得给img
     

    alt

    属性添加对应的属性值

但不是所有的图片都是使用

img

元素,对吧?你可能会用一个两个SVG或者一套SVG图标。

我们如何让每个人都能访问SVG?幸运的是,SVG(Scalable Vector Graphics)标准已经覆盖面已经很广!为了描述我们的SVG,我们可以使用

 

 

元素来进行简述和详细的描述。

给视频添加标题和子标题

这可能是WCAG最麻烦的原则之一,这不是因为技术上的困难,而是因为它可能是费时的。不过有一些方法可以做到这一点:

  • 让我们以YouTube为例。一旦你在这个平台上上传了一个视频,你就可以启用关闭标题。这些都是自动生成的,可能在某些情况下是不准确的,这取决于语言、背景噪音或说话人的口音。不过,这些都很容易实现,而且在大多数讲英语的视频中都能很好的工作
  • 如果我们很难看到百分百准确的标题,很难相信YouTube会有好的复制,所以我们必须自己写标题,或者雇佣第三方人员来做。如果我们不用任何字幕软件,或者我们希望我们的社区帮助我们翻译内容,YouTube将采用最常的副标题格式(
    .srt

    .sub

    .sbv

    )以及让我们在平台上写副标题。而不让管理员访问我们的账号,这将是非常方便的

web-accessibility-translating-subtitles-youtube-tool-1

  • 也许你不想把YouTube当作你的主机平台。也许您希望在你的服务器上使用一个HTML5视频。HTML5有一个
     

    标签,可以使用它方便让你添加你喜欢的标题和字幕软件,你可以使用你喜欢的WebVTT(翻译FTW)


语义等于可访问性

font

标签,还记得吗?我希望你不记得,那些是“古时代”的产物了。

尽管我们有着共同的信念,但语义并不是与生俱来的。自从第一个HTML页面诞生以来,他们一直与我们在一起,自那时候已经有了很大的进步。有了HTML5标准之后,新的语义标签就被引入到我们的日常使用当中。

web-accessibility-first-website

那么,语义不是仅仅为了SEO吗?

不一定。当您有意识地在&lt;p&gt;

&lt;span&gt;

选择中使用了

&lt;h1&gt;

标签时,这也意味着你也故意更改元素的含义,提供了层次结构,同时也构建页面信息的树形结构。

屏幕阅读器并没有忘记这一点。事实上,语义化是它最有用的武器之一

请记住,拥有强大的能力会带来很大的责任,所以一定要为每个元素使用合适的语义标签,从

 
h1

到全新main的

 

标签。

使用正确的标记

作为一个后续的观点,我想讨论一些不友好和有争议的地方。

Time vs. Datetime

time

元素显示了日期格式、时区的很多类型和使用ISO 8601标准来表示日期和时间的时间。

datetime

是一个可选属性,可以帮助表示time

 

的内容。让我们看看一些例子:

 

del

ins

Web不断变化,但没有必要让这些变化被忽视。我们可以将

ins

del

这样的HTML标签与

datetime

属性结合在一起使用。

ins

元素表示添加了一个文档:

del

元素表示删除一个内容:

button

vs. <a>

 

对于

button

和<a>

 

标签,我们什么时候使用更合适呢?

我们一起来看看。

&lt;a&gt;

元素的意思是链接一个文件或打开一个新标签或当前的链接。然而,每当我们希望触发一个Hamburger菜单或图片画廊之类的动作时,这个标签就有点不理想。

button

元素对于这些情况就更为比较适合,而且通常可以用JavaScript实现。

此外,

button

标签很容易与

input type="button"

混淆,但他们的区别在于前者能够获取更多的内容(文、图像加文或仅图像)。

当使用

button

标签时,有两件事情需要考虑:

首先,如果

button

的内容不够明确(例如,在关闭按钮中使用

X

),我们必须添加一个

aria-label

属性来帮助解释其功能。

其次,如果添加

href

属性是意义的(比如搜索组件或lightbox gallery),那么我们不仿使用一个

a

标签和使用JavaScript来覆盖链接行为。如果没有启用JavaScript,使用带有

href

标记的图像库将优雅地降级。

必要时使用

role

为了告诉屏幕阅读器用户,我们的链接触发了一个动作,实际上它不是一个普通的<a>

 

标签,使用

 

标签时,我们必须给他添加一个

role

属性。

但是要小心。

当你编写你的JavaScript时,你不仅需要在点击时调用你的函数,还需要在用户按下空格键时调用你的函数。这是必要的,因为用于按钮的行为不同于用于链接的行为,用户应该能够触发这些命令中的任何一个操作。

有关于这方面更多的信息可以在MDN上了解。

记住,除非你打破规则,否则通常不需要aria role规则,比如上面的示例。HTML语义元素已经应用了默认的

role

,比如

nav

标记表示的是导航,

 

标签表示的是链接等等。这意味着当我们希望更改这些默认值时,

role

属性只是必需的。

隐藏元素

使用HTML和CSS有一些方法可以隐藏东西。下面的列表将帮助你找到每一种情况的最佳选择:

方法 行为 屏幕阅读器行为 兼容性
CSS:

visibility:hidden;
从视觉中隐藏元素,但其原始空间仍然被占用(很像

opacity:0

不可读 到处可用(兼容性好)
CSS:

display:none;
从视觉中隐藏元素,它的原始空间丢失,下一个元素将取代它的位置 不可读 到处可用(兼容性好)
HTML5:

hidden

属性

类似于

display: none;
不可读 IE11+
aria-hidden = "true"
内容会显示在浏览器中,但通过技术不会传递给用户 不可读 IE11+
CSS:

.visuallyHidden

从视觉中隐藏元素,并从工作流中删除它 可读 到处可用(兼容性好)

如果你想隐藏元素,但仍然让屏幕阅读器可以知道它们,那么最后一个选项是最好的选择。

这在表单的

label

或Skip-to-content链接中非常有用。

visuallyHidden

类是一个CSS代码,它什么你收藏,因为每个项目都容易用到它。是的,如果你愿意,你可以改名字(我的建议是

.pottersCloak

)。

遵循Web可访问性标准

Web可访问性标准和指南在这里可以给我们提供很强的帮助。

本文前面的所有要点都是

button

是如何工作的?我们应该什么时候使用它?

display:none

hidden

属性的区别是什么?

首先,它可能是非常枯燥的,但W3C标准WCAG指南非常有用,它们还有非常强的意义。去吧,在他们当中提供了很多可用的信息。我向你保证,你将发现你从未接触的代码和实践!

Audit and review

一旦你应用到了所有这方面的知识,那就到了测试它的时候了。以下提供了一些工具测试你的网站可访问性:

Aerolab的可访问性经验

我们试着养成经常测试的工作习惯。我们的下一个产品应该总是比上一个更好。虽然我们有时候会犯错,但我们会不断地去改进,更不用说从每个挑战中学习一些东西了。

我们希望我们的产品能为用户提供最好的体验,这就是为什么我们逐渐开始将可访问性标准添加到我们的工作流程中。

我们还有很长的路要走,还有些要改进的空间,但我们很高兴选择了这条路。

我们为Xapo所做的登录页面就是我们如何给网站添加可访问性标准的一个例子,你可以从这个例子中查到这方面的使用代码:

web-accessibility-xapo-network-landing-page

总结

网站可访问性并不是那么容易就能实现,但如果你能把它变成你日常工作的一部分(而不是最后才来检查),随着时间的推移,它的实现和测试就会变得很容易。

当有疑问时,不要害怕询问其他的开发人员或者做一些这方面的研究。我最喜欢从这些渠道获取一些有用的信息:

另外,如果你有更多更好的资源,请在下面的评论中告诉我们。

null == undefined ?

原文出处: 一像素

最近在看《JavaScript高级程序设计》一书,书中讲到相等操作符(==)时说,要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,但要记住 null == undefined 会返回 true 。的确,在ECMAScript规范中也是这样定义的,但我认为这样来理解这件事情,似乎有些浮于表面,网上也有很多关于这个问题的文章,下面我希望从一个全新的角度来分析 null 和 undefined 的区别,从而理解两者为何会相等:

Undefined 和 Null 是 Javascript 中两种特殊的原始数据类型(Primary Type),它们都只有一个值,分别对应 undefined 和 null ,这两种不同类型的值,既有着不同的语义和场景,又表现出较为相似的行为:

1、undefined

undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果 。 这种原始状态会在以下 4 种场景中出现:

【1】声明了一个变量,但没有赋值

1
2
var foo;
console.log(foo); //undefined

访问 foo,返回了 undefined,表示这个变量自从声明了以后,就从来没有使用过,也没有定义过任何有效的值,即处于一种原始而不可用的状态。

【2】访问对象上不存在的属性

1
2
3
console.log(Object.foo); // undefined
var arr = [];
console.log([0]); // undefined

访问 Object 对象上的 foo 属性,返回 undefined , 表示Object 上不存在或者没有定义名为 foo 的属性。数组中的元素在内部也属于对象属性,访问下标就等于访问这个属性,返回 undefined ,就表示数组中不存在这个元素。

【3】函数定义了形参,但没有传递实参

1
2
3
4
5
//函数定义了形参 a
function fn(a) {
    console.log(a); //undefined
}
fn(); //未传递实参

函数 fn 定义了形参 a, 但 fn 被调用时没有传递参数,因此,fn 运行时的参数 a 就是一个原始的、未被赋值的变量。

【4】使用 void 对表达式求值

1
2
3
4
5
void 0 ; // undefined
void false; //undefined
void []; //undefined
void null; //undefined
void function fn(){} ; //undefined

ECMAScript 明确规定 void 操作符 对任何表达式求值都返回 undefined ,这和函数执行操作后没有返回值的作用是一样的,JavaScript 中的函数都有返回值,当没有 return 操作时,就默认返回一个原始的状态值,这个值就是 undefined,表明函数的返回值未被定义。

因此,undefined 一般都来自于某个表达式最原始的状态值,不是人为操作的结果。当然,你也可以手动给一个变量赋值 undefined,但这样做没有意义,因为一个变量不赋值就是 undefined 。

2、null

null 的字面意思是:空值  。这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象,即:

当一个对象被赋值了null 以后,原来的对象在内存中就处于游离状态,GC 会择机回收该对象并释放内存。因此,如果需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。试想一下,如果此处把 null 换成 undefined 会不会感到别扭? 显然语义不通,其操作不能正确的表达其想要的行为。

与 null 相关的另外一个问题需要解释一下:

1
typeof null == ‘object’

null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

  • 000 – 对象,数据是对象的应用
  • 1 – 整型,数据是31位带符号整数
  • 010 – 双精度类型,数据是双精度数字
  • 100 – 字符串,数据是字符串
  • 110 – 布尔类型,数据是布尔值

其实,我们可以通过另一种方法获取 null 的真实类型:

1
Object.prototype.toString.call(null) ; // [object Null]

通过 Object 原型上的toString() 方法可以获取到JavaScript 中对象的真实数据类型,当然 undefined 类型也可以通过这种方式来获取:

1
Object.prototype.toString.call(undefined) ; // [object Undefined]

3、相似性

虽然 undefined 和 null 的语义和场景不同,但总而言之,它们都表示的是一个无效的值。 因此,在JS中对这类值访问属性时,都会得到异常的结果:

1
2
null.toString(); // Cannot read property ‘toString’ of null
undefined.toString(); // Cannot read property ‘toString’ of undefined

ECMAScript 规范认为,既然 null 和  undefined 的行为很相似,并且都表示 一个无效的值,那么它们所表示的内容也具有相似性,即有

1
undefined == null; //true

不要试图通过转换数据类型来解释这个结论,因为:

1
2
3
4
5
Number(null); // 0
Number(undefined); // NaN
//在比较相等性之前,null 没有被转换为其他类型
null == 0 ; //false

但 === 会返回 false ,因为全等操作 === 在比较相等性的时候,不会主动转换分项的数据类型,而两者又不属于同一种类型:

1
2
undefined === null; //false,类型不相同
undefined !== null;  //true, 类型不相同

4、总结

用一句话总结两者的区别就是:undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。

 

原创发布 @一像素 2017.08