今天跟大家分享下处理JS中undefined的7个技巧的知识。
前言
大约8年前,开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,比较null == undefined的计算结果为true。
大多数现代语言,如Ruby、Python或Java都有一个空值(nil或null),这似乎是一种合理的方式。
对于JavaScript,解释器在访问尚未初始化的变量或对象属性时返回undefined。例如:
let company;
company; // => undefined
let person = {
name: 'John Smith' };
person.age; // => undefined
另一方面,null表示缺少的对象引用,JS本身不会将变量或对象属性设置为null。
一些原生方法,比如String.prototype.match(),可以返回null来表示丢失的对象。看看下面的示例:
let array = null;
array; // => null
let movie = {
name: 'Starship Troopers', musicBy: null };
movie.musicBy; // => null
'abc'.match(/[0-9]/); // => null
由于 JS 的宽容特性,开发人员很容易访问未初始化的值,我也犯了这样的错误。
通常,这种危险的操作会生成undefined 的相关错误,从而快速地结束脚本。相关的常见错误消息有:
TypeError: 'undefined' is not a function
TypeError: Cannot read property '<prop-name>' of undefined
type errors
JS 开发人员可以理解这个笑话的讽刺:
functionundefined(){
// problem solved
}
为了降低此类错误的风险,必须理解生成undefined的情况。更重要的是抑制它的出现并阻止在应用程序中传播,从而提高代码的持久性。
让咱们详细讨论undefined 及其对代码安全性的影响。
1 undefined是什么鬼
JS 有6种基本类型
Boolean: true 或 false
Number: 1, 6.7, 0xFF
String: “Gorilla and banana”
Symbol: Symbol(“name”) (starting ES2015)
Null: null
Undefined: undefined.
和一个单独的Object 类型:{name: “Dmitri”}, [“apple”, “orange”]。
根据ECMAScript规范,从6种原始类型中,undefined是一个特殊的值,它有自己的Undefined类型。
未为变量赋值时默认值为undefined。
该标准明确定义,当访问未初始化的变量、不存在的对象属性、不存在的数组元素等时,将接收到一个undefined 的值。例如
let number;
number; // => undefined
let movie = {
name: 'Interstellar' };
movie.year; // => undefined
let movies = ['Interstellar', 'Alexander'];
movies[3]; // => undefined
上述代码大致流程:
未初始化的变量number
一个不存在的对象属性movie.year
或者不存在数组元素movies[3]
都会被定义为undefined。
ECMAScript规范定义了undefined 值的类型
Undefined type是其唯一值为undefined 值的类型。
在这个意义上,typeof undefined返回“undefined”字符串
typeofundefined==='undefined';// => true
当然typeof可以很好地验证变量是否包含undefined的值
let nothing;
typeofnothing ==='undefined';// => true
2 创建未定义的常见场景
2.1 未初始化变量
尚未赋值(未初始)的声明变量默认为undefined。
let myVariable;
myVariable;// => undefined
myVariable已声明,但尚未赋值,默认值为undefined。
解决未初始化变量问题的有效方法是尽可能分配初始值。变量在未初始化状态中越少越好。理想情况下,你可以在声明const myVariable ='Initial value’之后立即指定一个值,但这并不总是可行的。
技巧1:使用 let 和 const 来代替 var
在我看来,ES6 最好的特性之一是使用const和let声明变量的新方法。const和let具有块作用域(与旧的函数作用域var相反),在声明行之前都存在于暂时性死区。
当变量一次性且永久地接收到一个值时,建议使用const声明,它创建一个不可变的绑定。
const的一个很好的特性是必须为变量const myVariable ='initial’分配一个初始值。变量未暴露给未初始化状态,并且访问undefined是不可能的。
以下示例检查验证一个单词是否是回文的函数:
function isPalindrome(word) {
const length = word.length;
const half = Math.floor(length / 2);
for (let index = 0; index < half; index++) {
if (word[index] !== word[length - index - 1]) {
return false;
}
}
return true;
}
isPalindrome('madam'); // => true
isPalindrome('hello'); // => false
length 和 half 变量被赋值一次。将它们声明为const似乎是合理的,因为这些变量不会改变。
如果需要重新绑定变量(即多次赋值),请应用let声明。只要可能,立即为它赋一个初值,例如,let index = 0。
那么使用 var 声明呢,相对于ES6,建议是完全停止使用它。
var 声明的变量提会被提升到整个函数作用域顶部。可以在函数作用域末尾的某个地方声明var变量,但是仍然可以在声明之前访问它:对应变量的值是 undefined。
相反,用let 或者 const 声明的变量之前不能访问该变量。之所以会发生这种情况,是因为变量在声明之前处于暂时死区。这很好,因为这样就很少有机会访问到 undefined 值。
使用let(而不是var)更新的上述示例会引发ReferenceError 错误,因为无法访问暂时死区中的变量。
function bigFunction() {
// code...
myVariable; // => Throws 'ReferenceError: myVariable is not defined'
// code...
let myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();
技巧2:增加内聚性
内聚描述模块的元素(命名空间、类、方法、代码块)内聚在一起的程度。凝聚力的测量通常被称为高凝聚力或低内聚。
高内聚是优选的,因为它建议设计模块的元素以仅关注单个任务,它构成了一个模块。
专注且易懂:更容易理解模块的功能
可维护且更容易重构:模块中的更改会影响更少的模块
可重用:专注于单个任务,使模块更易于重用
可测试:可以更轻松地测试专注于单个任务的模块
高内聚和低耦合是一个设计良好的系统的特征。
代码块本身可能被视为一个小模块,为了尽可能实现高内聚,需要使变量尽可能接近使用它们代码块位置。
例如,如果一个变量仅存在以形成块作用域内,不要将此变量公开给外部块作用域,因为外部块不应该关心此变量。
不必要地延长变量生命周期的一个典型例子是函数中for循环的使用:
function someFunc(array) {
var index, item, length = array.length;
// some code...
//