当原作者开始学习JS时,遇到了一个奇怪的情况,既存在undefined 的值,也存在表示空值的null。它们之间的明显区别是什么?它们似乎都定义了空值,而且,比较null == undefined的计算结果为true。
![]() 当原作者开始学习JS时,遇到了一个奇怪的情况,既存在 大多数现代语言,如Ruby、Python或Java都有一个空值( 对于JavaScript,解释器在访问尚未初始化的变量或对象属性时返回 let company;
company; // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined另一方面, 一些原生方法,比如 let array = null;
array; // => null
let movie = { name: "Starship Troopers", musicBy: null };
movie.musicBy; // => null
"abc".match(/[0-9]/); // => null由于 JS 的宽容特性,开发人员很容易访问未初始化的值,我也犯了这样的错误。 通常,这种危险的操作会生成
JS 开发人员可以理解这个笑话的讽刺: function undefined() {
// problem solved
}为了降低此类错误的风险,必须理解生成 让咱们详细讨论 1、undefined 是什么鬼JS 有6种基本类型
和一个单独的 根据ECMAScript规范,从6种原始类型中,
该标准明确定义,当访问未初始化的变量、不存在的对象属性、不存在的数组元素等时,将接收到一个 let number;
number; // => undefined
let movie = { name: "Interstellar" };
movie.year; // => undefined
let movies = ["Interstellar", "Alexander"];
movies[3]; // => undefined上述代码大致流程:
都会被定义为 ECMAScript规范定义了
在这个意义上, typeof undefined === "undefined"; // => true 当然 let nothing; typeof nothing === "undefined"; // => true 2、导致undefined的常见场景2.1 未初始化变量 尚未赋值(未初始化)的声明变量默认为 let myVariable; myVariable; // => undefined
解决未初始化变量问题的有效方法是尽可能分配初始值。 变量在未初始化状态中越少越好。 理想情况下,你可以在声明 技巧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"); // => falselength 和 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:增加内聚性 内聚描述模块的元素(命名空间、类、方法、代码块)内聚在一起的程度。凝聚力的测量通常被称为高凝聚力或低内聚。 高内聚是优选的,因为它建议设计模块的元素以仅关注单个任务,它构成了一个模块。
高内聚和低耦合是一个设计良好的系统的特征。 代码块本身可能被视为一个小模块,为了尽可能实现高内聚,需要使变量尽可能接近使用它们代码块位置。 例如,如果一个变量仅存在以形成块作用域内,不要将此变量公开给外部块作用域,因为外部块不应该关心此变量。 不必要地延长变量生命周期的一个典型例子是函数中 function someFunc(array) {
var index, item, length = array.length;
// some code...
// some code...
for (index = 0; index < length; index++) {
item = array[index];
// some code...
}
return 'some result';
}
从顶部的声明到 一种更好的方法是将这些变量尽可能地移动到使用它们的位置: function someFunc(array) {
// some code...
// some code...
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// some
}
return 'some result';
}
为什么修改后的版本优于初始版本? 主要有几点:
2.2 访问不存在的属性
咱们用一个例子来说明这一点: let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined
本身访问不存在的属性不会引发错误, 但尝试从不存在的属性值中获取数据时就会出现问题。 常见的的错误是 稍微修改前面的代码片段来说明 let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined
JS 允许访问不存在的属性,这种允许访问的特性容易引起混淆:可能设置了属性,也可能没有设置属性,绕过这个问题的理想方法是限制对象始终定义它所持有的属性。 不幸的是,咱们常常无法控制对象。在不同的场景中,这些对象可能具有不同的属性集,因此,必须手动处理所有这些场景: 接着我们实现一个函数
函数返回一个新的数组实例,而不改变原始数组(即它是一个纯函数)。
function append(array, toAppend) {
const arrayCopy = array.slice();
if (toAppend.first) {
arrayCopy.unshift(toAppend.first);
}
if (toAppend.last) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append(['Hello'], { last: 'World' }); // => ['Hello', 'World']
append([8, 16], { first: 4 }); // => [4, 8, 16]由于 检查 这种方法有一个缺点, 在 append([10], { first: 0, last: false }); // => [10]
以下技巧解释了如何正确检查属性的存在。 技巧3: 检查属性是否存在 JS 提供了许多方法来确定对象是否具有特定属性:
我的建议是使用
涉及与undefined进行比较剩下的两种方式可能有效,但在我看来, 让咱们使用 function append(array, toAppend) {
const arrayCopy = array.slice();
if ('first' in toAppend) {
arrayCopy.unshift(toAppend.first);
}
if ('last' in toAppend) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append([10], { first: 0, last: false }); // => [0, 10, false]
技巧4:解构访问对象属性 在访问对象属性时,如果属性不存在,有时需要指示默认值。可以使用 const object = { };
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'当要检查的属性数量增加时,三元运算符语法的使用变得令人生畏。对于每个属性,都必须创建新的代码行来处理默认值,这就增加了一堵难看的墙,里面都是外观相似的三元运算符。 为了使用更优雅的方法,可以使用 ES6 对象的解构。 对象解构允许将对象属性值直接提取到变量中,并在属性不存在时设置默认值,避免直接处理 实际上,属性提取现在看起来简短而有意义: const object = { };
const { prop = 'default' } = object;
prop; // => 'default'要查看实际操作中的内容,让我们定义一个将字符串包装在引号中的有用函数。
使用对象析构的优点,让咱们实现quote() function quote(str, config) {
const { char = '"', skipIfQuoted = true } = config;
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'
该功能仍有改进的空间。让我们将解构赋值直接移动到参数部分。并为 function quote(str, { char = '"', skipIfQuoted = true } = {}) {
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('Sunny day'); // => '"Sunny day"'注意,解构赋值替换了函数
对象解构是一个强大的功能,可以有效地处理从对象中提取属性。 我喜欢在被访问属性不存在时指定要返回的默认值的可能性。因为这样可以避免 技巧5: 用默认属性填充对象 如果不需要像解构赋值那样为每个属性创建变量,那么丢失某些属性的对象可以用默认值填充。 ES6 例如,需要访问 为了避免从 定义包含默认属性值的 调用 const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = Object.assign({}, defaults, unsafeOptions);
options.fontSize; // => 18
options.color; // => 'black'
枚举源对象的顺序很重要:后面的源对象属性会覆盖前面的源对象属性。 现在可以安全地访问 还有一种简单的方法就是使用ES6中展开运算符: const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'对象初始值设定项从 使用默认属性值填充不完整的对象是使代码安全且持久的有效策略。无论哪种情况,对象总是包含完整的属性集:并且无法生成undefined的属性。 2.3 函数参数
通常,用特定数量的参数定义的函数应该用相同数量的参数调用。在这种情况下,参数得到期望的值 function multiply(a, b) {
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15调用 在调用时省略参数会发生什么? function multiply(a, b) {
a; // => 5
b; // => undefined
return a * b;
}
multiply(5); // => NaN函数 技巧6: 使用默认参数值 有时函数不需要调用的完整参数集,可以简单地为没有值的参数设置默认值。 回顾前面的例子,让我们做一个改进,如果 function multiply(a, b) {
if (b === undefined) {
b = 2;
}
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10虽然所提供的分配默认值的方法有效,但不建议直接与 这里可以使用 ES6 的默认值: function multiply(a, b = 2) {
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 102.4 函数返回值
在JS中,没有任何 function square(x) {
const res = x * x;
}
square(2); // => undefined
当 function square(x) {
const res = x * x;
return;
}
square(2); // => undefined
function square(x) {
const res = x * x;
return res;
}
square(2); // => 4技巧7: 不要相信自动插入分号 JS 中的以下语句列表必须以分号
如果使用上述声明之一,请尽量务必在结尾处指明分号: function getNum() {
let num = 1;
return num;
}
getNum(); // => 1
当你不想写这些分号时会发生什么? 例如,咱们想要减小源文件的大小。 在这种情况下,ECMAScript 提供自动分号插入(ASI)机制,为你插入缺少的分号。 ASI 的帮助下,可以从上一个示例中删除分号: function getNum() {
// Notice that semicolons are missing
let num = 1
return num
}
getNum() // => 1上面的代码是有效的JS代码,缺少的分号ASI会自动为我们插入。 乍一看,它看起来很 nice。 ASI 机制允许你少写不必要的分号,可以使JS代码更小,更易于阅读。 ASI 创建了一个小而烦人的陷阱。 当换行符位于 函数内部 来 function getPrimeNumbers() {
return
[ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined在 function getPrimeNumbers() {
return;
[ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined
这个问题通过删除 function getPrimeNumbers() {
return [
2, 3, 5, 7, 11, 13, 17
];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]我的建议是研究自动分号插入的确切方式,以避免这种情况。 当然,永远不要在 2.5 void 操作符
void 1; // => undefined
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined
3、 未定义的数组访问越界索引的数组元素时,会得到 const colors = ['blue', 'white', 'red']; colors[5]; // => undefined colors[-1]; // => undefined
因为索引 JS 中,可能会遇到所谓的稀疏数组。这些数组是有间隙的数组,也就是说,在某些索引中,没有定义元素。 当在稀疏数组中访问间隙(也称为空槽)时,也会得到一个 下面的示例生成稀疏数组并尝试访问它们的空槽 const sparse1 = new Array(3); sparse1; // => [<empty slot>, <empty slot>, <empty slot>] sparse1[0]; // => undefined sparse1[1]; // => undefined const sparse2 = ['white', ,'blue'] sparse2; // => ['white', <empty slot>, 'blue'] sparse2[1]; // => undefined 使用数组时,为了避免获取 4、 undefined和null之间的区别一个合理的问题出现了: 主要区别在于 让咱们通过一些例子来探讨它们之间的区别。 number 定义了但没有赋值。 let number; number; // => undefined
当访问不存在的对象属性时,也会发生相同的未初始化概念 const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined因为 在其他情况下,你知道变量期望保存一个对象或一个函数来返回一个对象。但是由于某些原因,你不能实例化该对象。在这种情况下,null是丢失对象的有意义的指示器。 例如, function clone(obj) {
if (typeof obj === 'object' && obj !== null) {
return Object.assign({}, obj);
}
return null;
}
clone({name: 'John'}); // => {name: 'John'}
clone(15); // => null
clone(null); // => null但是,可以使用非对象参数调用 typeof操作符区分了这两个值 typeof undefined; // => 'undefined' typeof null; // => 'object' 严格相等运算符 let nothing = undefined; let missingObject = null; nothing === missingObject; // => false 总结
大多数情况下直接与
更多编程相关知识,请访问:编程入门!! 以上就是JavaScript中7个处理undefined的小技巧的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
