尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入理解 JavaScript 内置类型:typeof、undefined 与 undeclared 的真相 —— 解读《You Don‘t Know JS》「类型与语法」第 1 章

深入理解 JavaScript 内置类型:typeof、undefined 与 undeclared 的真相 —— 解读《You Don‘t Know JS》「类型与语法」第 1 章 教程文档【免费下载链接】you-dont-know-js-ru Russian translation of You Dont Know JS book series项目地址https://gitcode.com/gh_mirrors/yo/you-dont-know-js-ru点击查看免费下载导读本文以《You Dont Know JS》系列俄语翻译仓库中 types grammar/ch1.mdТипы и грамматика 第 1 章为骨架系统讲解 JavaScript 的七种内置类型、typeof运算符的真实返回值以及开发者最易混淆的undefined未定义与undeclared未声明两个概念。读完本文你将掌握用typeof安全探测全局变量、区分函数与数组的真实类型地位、理解变量无类型、值有类型这一核心心智模型并为后续章节值、内建对象、强制类型转换打下基础。一、JavaScript 到底有没有类型很多开发者会脱口而出JavaScript 是动态语言所以它没有类型。但 ES5.1 规范在类型一节中给出了明确表述规范中的算法操作的是值每个值都关联着一种类型。可用的值类型正是本节所定义的这些。类型进一步区分为 ECMAScript 语言类型和规范类型。ECMAScript 语言类型对应着程序员直接用 ECMAScript 语言操作的值它们是Undefined、Null、Boolean、String、Number 与 Object。从静态类型语言如 Java、C的视角看类型这个词的含义远不止于此于是有人建议把 JS 的类型改叫标签tag或子类型。本书作者给出了一个务实的粗粒度定义类型是一组内建特征它唯一地标识特定值的行为并将其与其他值区分开来——无论对引擎还是对开发者都是如此。换句话说引擎和开发者对待42number的方式不同于对待42string——一个用于数学计算一个用于页面输出——所以它们就是不同的类型。理解每个类型及其内部行为是正确、精确地执行类型转换强制转换详见 ch4.md的前提。几乎每一个 JS 程序都要以某种形式处理值的转换因此负责任地、自信地处理转换至关重要。42是number但如果你想把它当字符串用比如取出位置1的字符2就必须先把number转换为string。转换方式很多有的清晰、可靠有的则在你不注意时表现得非常奇怪。类型转换coercion常被诟病为 JS 最危险、最应避开的特性之一但本书的观点是这种坏名声被夸大了——一旦你对类型有了完整理解就能看到强制转换的力量与用途。不过在谈转换之前先要把值和类型本身弄清楚。二、七种内置类型与 typeof 的对应关系JavaScript 定义了七种内置类型nullundefinedbooleannumberstringobjectsymbolES6 新增除object之外的六种统称为原始类型primitives。typeof运算符用于检测给定值的类型它总是返回七种字符串值之一——有趣的是这七个字符串与上面列出的七种内置类型并非一一对应typeof undefined undefined; // true typeof true boolean; // true typeof 42 number; // true typeof 42 string; // true typeof { life: 42 } object; // true // ES6 新增 typeof Symbol() symbol; // true前六种类型都能得到与类型名一致的返回值symbol是 ES6 的新数据类型其详细内容见第 3 章ch3.md。2.1 神秘的第七个返回值typeof null 的bug注意上面列表中我故意漏掉了null——它在typeof面前抽风了typeof null object; // true理想情况下这里应该返回null但这是 JS 自诞生起就存在、延续了近二十年的原始 bug而且几乎永远不会被修复——因为 Web 上有太多代码依赖这一错误行为修复它反而会制造出更多 bug、破坏大量现有网页程序。如果你想按类型检测null需要借助复合条件var a null; (!a typeof a object); // truenull是唯一一个假值falsy详见 ch4.md 关于布尔转换的章节却又被typeof报成object的原始值。2.2 typeof 的第七个字符串函数是可调用对象那么typeof返回的第七个字符串值是什么typeof function a(){ /* .. */ } function; // true看到function很容易误以为函数是 JS 的顶层内置类型。但查阅规范会发现函数实际上是object的子类型subtype准确地说叫可调用对象callable object——即拥有内部属性[[Call]]、可以被调用的对象。函数即对象这一事实非常有用——最典型的是函数可以拥有属性。例如function a(b,c) { /* .. */ }函数对象自带length属性其值等于声明时形参的个数a.length; // 2因为a声明了两个形参b和c所以函数长度是2。2.3 数组也不是特殊类型数组是原生类型那它特殊吗typeof [1,2,3] object; // true不数组就是普通对象。最合理的理解是把数组也看作object的子类型第 3 章会展开ch3.md——它在普通对象的字符串键之外额外具备数字索引特性并支持自动更新的.length属性。第 2 章ch2.md对数组的数值索引、字符串键、稀疏数组等行为还有专门讨论。小结typeof的七个返回值是undefined、boolean、number、string、object、symbol和function其中null报object是历史遗留 bugfunction与array本质上都是object的子类型。三、值才有类型变量没有类型在 JavaScript 中变量没有类型——值才有类型。变量可以在任意时刻存放任意值。换句话说JS 不存在类型强制约束引擎不会要求一个变量始终持有它初始化时的同类型值。变量可以在一条赋值语句中是string下一条就变成number。42这个值的内置类型是number且这个类型不可改变而像42这样的string值只能通过强制类型转换详见第 4 章 ch4.md从number的42派生而来。当你对变量使用typeof时它问的并不是这个变量的类型是什么变量没有类型而是这个变量里存的值是什么类型var a 42; typeof a; // number a true; typeof a; // boolean另外typeof永远返回字符串所以typeof typeof 42; // string先执行typeof 42得到number再对number执行typeof得到string。四、undefined 与 undeclared完全不同的两回事暂时没有值的变量其值实际上是undefined。对这类变量调用typeof返回undefinedvar a; typeof a; // undefined var b 42; var c; // 之后…… b c; typeof b; // undefined typeof c; // undefined大多数开发者习惯把 undefined未定义当作 undeclared未声明的同义词但在 JS 中这是两个截然不同的概念undefined未定义变量已在可访问的作用域中声明但当前没有值undeclared未声明变量在可访问的作用域中根本没有被声明过。对照看var a; a; // undefined b; // ReferenceError: b is not defined浏览器给这种场景分配的报错信息加剧了混乱——它写的是b is not defined极易被误读为b 是 undefined。实际上undefined与not defined完全是两码事。如果浏览器能改成b not found或b not declared之类混乱会少得多。而typeof对未声明变量还有一个加剧混淆的特殊行为var a; typeof a; // undefined typeof b; // undefinedtypeof对未声明的变量同样返回undefined——注意执行typeof b时没有抛错尽管b从未声明。这是typeof的特殊安全机制special safety valve。一个理想设计是typeof对未声明变量返回undeclared而不是把两种情形合并成undefined。但现实就是如此因此理解并善用这一行为才是正道。五、typeof 的安全机制安全探测全局与宿主变量尽管上述行为容易引起混淆但未声明变量 typeof 不抛错这个安全机制在浏览器多脚本文件共享全局命名空间的场景下非常有用。很多开发者主张全局命名空间应该保持干净、一切内容都应放入模块和封闭的独立命名空间。这在理论上很美好实践中却几乎不可能但仍值得作为目标追求。好在 ES6 引入了头等公民的模块支持让这件事变得更容易。5.1 经典场景DEBUG 调试开关假设程序有一个由全局变量DEBUG控制的调试模式。全局var DEBUG true只出现在debug.js中而该文件只在开发/测试环境加载、生产环境不加载。那么应用其余代码在检查DEBUG时必须小心否则会抛出ReferenceError——此时typeof的安全机制就是你的朋友// 糟糕这里会报错 if (DEBUG) { console.log( Debugging is starting ); } // 这是安全的存在性检查 if (typeof DEBUG ! undefined) { console.log( Debugging is starting ); }5.2 特性检测与 polyfill这种检查即使对非用户自定义的变量如DEBUG之外的内建 API 同样适用——做特性检测、需要不抛错的检查时if (typeof atob undefined) { atob function() { /*..*/ }; }注意如果你在为尚不存在的能力写 polyfill应避免用var声明atob。因为var atob的声明会提升hoisting详见系列书 scope closures到作用域顶部即使if条件不成立全局atob已存在也会执行声明。在某些浏览器中对特定全局内建常称 host 对象的重复声明可能引发错误。省略var可以防止声明提升。5.3 另一种方式window 全局对象不借助typeof检查全局变量的另一种办法是全局变量同时是全局对象浏览器中通常是window的属性。因此上面的检查可以这样写if (window.DEBUG) { // .. } if (!window.atob) { // .. }与引用未声明变量不同访问对象属性即使是全局window对象不会抛出ReferenceError。但反过来说显式通过window访问全局变量是部分开发者刻意回避的做法——尤其是代码需要运行在多种 JS 环境不只是浏览器还有服务端 node.js 等时全局变量并不总叫window。5.4 在非全局场景中的价值工具函数与 IIFEtypeof的防护机制即使不涉及全局变量也有用虽然条件更少见。想象一个你想复制粘贴到别人程序/模块中的工具函数它需要判断宿主程序是否定义了某个变量再决定是否使用function doSomethingCool() { var helper (typeof FeatureXYZ ! undefined) ? FeatureXYZ : function() { /*.. 默认函数 ..*/ }; var val helper(); // .. }doSomethingCool()探测名为FeatureXYZ的变量存在则使用不存在则回退到自带的默认实现。如果别人在自己的模块里引用了该工具它也能可靠地判断FeatureXYZ是否已定义。更典型的是在 IIFE立即调用函数表达式详见 scope closures 系列讨论内部使用——此时FeatureXYZ完全不是全局变量也没有window这样的对象可供探测typeof就显得格外有价值(function(){ function FeatureXYZ() { /*.. 我的 XYZ 函数 ..*/ } // 引入 doSomethingCool(..) function doSomethingCool() { var helper (typeof FeatureXYZ ! undefined) ? FeatureXYZ : function() { /*.. 默认函数 ..*/ }; var val helper(); // .. } doSomethingCool(); })();5.5 备选方案显式依赖注入另有开发者更偏好依赖注入dependency injection设计模式与其让doSomethingCool()隐式探测外部是否定义了FeatureXYZ不如把依赖显式传进来function doSomethingCool(FeatureXYZ) { var helper FeatureXYZ || function() { /*.. 默认函数 ..*/ }; var val helper(); // .. }两种模式各有细微差别没有绝对的对错。但总体而言typeof对未声明变量的防护机制为我们提供了更多选择空间。六、本章要点回顾JavaScript 有七种内置类型null、undefined、boolean、number、string、object、symbol可用typeof检测。变量没有类型值才有类型这些类型决定了值的内在行为。很多开发者以为未定义undefined与未声明undeclared是一回事但在 JS 中完全不同undefined是已声明变量可能持有的值undeclared表示变量从未被声明。不幸的是JS 在某种程度上把两者混为一谈——不仅在报错信息ReferenceError: a is not defined里typeof对两者也都返回undefined。但typeof作用于未声明变量时不会抛错的防护机制在若干场景全局调试开关、特性检测、polyfill、跨模块工具函数中非常实用。延伸阅读本主题与系列书及同册其他章节相互印证types grammar/toc.md本书完整目录本章第 1 章属于类型与语法上册。types grammar/ch2.md第 2 章深入数组、字符串、数字与特殊值其中数组的数字索引与字符串键行为是对本章数组是对象子类型的展开。types grammar/ch3.md第 3 章讲解String()、Number()等内建对象、[[Class]]内部属性与装箱/拆箱进一步印证包装对象是 object 子类型。types grammar/ch4.md第 4 章专门讨论强制类型转换显式与隐式、ToString/ToNumber/ToBoolean抽象操作正是本章反复预告的转换主题。types grammar/README.md本书前言与各章入口。scope closures/ch1.md系列书另一册本章提到的var声明提升、IIFE 等在作用域与闭包语境下有完整讲解。系列总览见仓库根目录 README.md 与 preface.md。赞分享教程文档【免费下载链接】you-dont-know-js-ru Russian translation of You Dont Know JS book series项目地址https://gitcode.com/gh_mirrors/yo/you-dont-know-js-ru点击查看免费下载相关推荐浏览器内ADB调试Tango如何革新Android开发体验浏览器内ADB调试Tango如何革新Android开发体验 你是否曾为Android开发调试的繁琐环境配置而烦恼传统ADB客户端需要复杂的本地环境设置限制开发工具通信移动开发CLI深入理解JavaScript ES6及新特性——《You Dont Know JS》核心解读深入理解JavaScript ES6及新特性——《You Dont Know JS》核心解读 JavaScript ES6是JavaScript语言的革命性升文档教程gh_mirrors/yo/you-dont-know-js-ru类与继承ES6类语法深入理解gh_mirrors/yo/you dont know js ru类与继承ES6类语法深入理解 你是否还在用构造函数原型链的方式模拟类是否觉得继承实现繁琐教程文档上一篇彻底解决Banner指示器错位问题attachToBanner参数实战指南下一篇一文读懂Patchwork架构核心组件与工作原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表