类型转换
JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。
// 下面的比较结果是:true
new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字
10 == '10'; // 字符串被转换为数字
10 == '+10 '; // 同上
10 == '010'; // 同上
isNaN(null) == false; // null 被转换为数字 0
// 0 当然不是一个 NaN(译者注:否定之否定)
// 下面的比较结果是:false
10 == 010;
10 == '-10';
ES5 提示:以0
开头的数字字面值会被作为八进制数字解析。而在
ECMAScript 5 严格模式下,这个特性被移除了。
为了避免上面复杂的强制类型转换,强烈推荐使用严格的等于操作符。虽然这可以避免大部分的问题,但
JavaScript 的弱类型系统仍然会导致一些其它问题。
内置类型的构造函数(Constructors of built-in types)
内置类型(比如Number
和String
)的构造函数在被调用时,使用或者不使用new
的结果完全不同。
new Number(10) === 10; // False, 对象与数字的比较
Number(10) === 10; // True, 数字与数字的比较
new Number(10) + 0 === 10; // True, 由于隐式的类型转换
使用内置类型Number
作为构造函数将会创建一个新的Number
对象,而在不使用new
关键字的Number
函数更像是一个数字转换器。
另外,在比较中引入对象的字面值将会导致更加复杂的强制类型转换。
最好的选择是把要比较的值显式的转换为三种可能的类型之一。
转换为字符串(Casting to a string)
'' + 10 === '10'; // true
将一个值加上空字符串可以轻松转换为字符串类型。
转换为数字(Casting to a number)
+'10' === 10; // true
使用一元的加号操作符,可以把字符串转换为数字。
译者注:字符串转换为数字的常用方法:
+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10 // 用来转换为整数
+'010.2' === 10.2
Number('010.2') === 10.2
parseInt('010.2', 10) === 10
转换为布尔型(Casting to a boolean)
通过使用否操作符两次,可以把一个值转换为布尔型。
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true
undefined
和null
JavaScript 有两个表示空
的值,其中比较有用的是undefined
。
`undefined`的值(The value `undefined`)
undefined
是一个值为undefined
的类型。
这个语言也定义了一个全局变量,它的值是undefined
,这个变量也被称为undefined
。但是这个变量不是一个常量,也不是一个关键字。这意味着它的值可以轻易被覆盖。
ES5 提示:在 ECMAScript 5 的严格模式下,undefined
不再是可写的了。但是它的名称仍然可以被隐藏,比如定义一个函数名为undefined
。
下面的情况会返回undefined
值:
-
访问未修改的全局变量
undefined
。
-
由于没有定义
return
表达式的函数隐式返回。
-
return
表达式没有显式的返回任何内容。
-
访问不存在的属性。
-
函数参数没有被显式的传递值。
-
任何被设置为
undefined
值的变量。
处理 `undefined` 值的改变(Handling changes to the value of `undefined`)
由于全局变量undefined
只是保存了undefined
类型实际值的副本,因此对它赋新值不会改变类型undefined
的值。
然而,为了方便其它变量和undefined
做比较,我们需要事先获取类型undefined
的值。
为了避免可能对undefined
值的改变,一个常用的技巧是使用一个传递到匿名包装器的额外参数。在调用时,这个参数不会获取任何值。
var undefined = 123;
(function(something, foo, undefined) {
// 局部作用域里的 undefined 变量重新获得了 `undefined` 值
})('Hello World', 42);
另外一种达到相同目的方法是在函数内使用变量声明。
var undefined = 123;
(function(something, foo) {
var undefined;
...
})('Hello World', 42);
这里唯一的区别是,在压缩后并且函数内没有其它需要使用var
声明变量的情况下,这个版本的代码会多出 4 个字节的代码。
译者注:这里有点绕口,其实很简单。如果此函数内没有其它需要声明的变量,那么var
总共
4 个字符(包含一个空白字符)就是专门为 undefined 变量准备的,相比上个例子多出了 4 个字节。
使用 `null`(Uses of `null`)
JavaScript 中的undefined
的使用场景类似于其它语言中的null,实际上
JavaScript 中的null
是另外一种数据类型。
它在 JavaScript 内部有一些使用场景(比如声明原型链的终结Foo.prototype = null
),但是大多数情况下都可以使用undefined
来代替。
为什么不要使用 `eval`
eval
函数会在当前作用域中执行一段 JavaScript 代码字符串。
var foo = 1;
function test() {
var foo = 2;
eval('foo = 3');
return foo;
}
test(); // 3
foo; // 1
但是eval
只在被直接调用并且调用函数就是eval
本身时,才在当前作用域中执行。
var foo = 1;
function test() {
var foo = 2;
var bar = eval;
bar('foo = 3');
return foo;
}
test(); // 2
foo; // 3
译者注:上面的代码等价于在全局作用域中调用eval
,和下面两种写法效果一样:
// 写法一:直接调用全局作用域下的 foo 变量
var foo = 1;
function test() {
var foo = 2;
window.foo = 3;
return foo;
}
test(); // 2
foo; // 3
// 写法二:使用 call 函数修改 `eval` 执行的上下文为全局作用域
var foo = 1;
function test() {
var foo = 2;
eval.call(window, 'foo = 3');
return foo;
}
test(); // 2
foo; // 3
在任何情况下我们都应该避免使用eval
函数。99.9%
使用eval
的场景都有不使用eval
的解决方案。
伪装的 `eval`(`eval` in disguise)
定时函数setTimeout
和setInterval
都可以接受字符串作为它们的第一个参数。这个字符串总是在全局作用域中执行,因此eval
在这种情况下没有被直接调用。
安全问题(Security issues)
eval
也存在安全问题,因为它会执行任意传给它的代码,在代码字符串未知或者是来自一个不信任的源时,绝对不要使用eval
函数。
结论(In conclusion)
绝对不要使用eval
,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。如果一些情况必须使用到eval
才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案,一个更好的不使用eval
的解决方案应该得到充分考虑并优先采用。
`setTimeout` 和 `setInterval`
由于 JavaScript 是异步的,可以使用setTimeout
和setInterval
来计划执行函数。
注意:定时处理不是ECMAScript
的标准,它们在DOM被实现。
function foo() {}
var id = setTimeout(foo, 1000); // 返回一个大于零的数字
当setTimeout
被调用时,它会返回一个 ID 标识并且计划在将来大约1000
毫秒后调用foo
函数。foo
函数只会被执行一次。
基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。因此没法确保函数会在setTimeout
指定的时刻被调用。
作为第一个参数的函数将会在全局作用域中执行,因此函数内的`this`将会指向这个全局对象。
function Foo() {
this.value = 42;
this.method = function() {
// this 指向全局对象
console.log(this.value); // 输出:undefined
};
setTimeout(this.method, 500);
}
new Foo();
注意:setTimeout
的第一个参数是函数对象,一个常犯的错误是这样的setTimeout(foo(),
1000)
,这里回调函数是foo
的返回值,而不是foo
本身。大部分情况下,这是一个潜在的错误,因为如果函数返回undefined
,setTimeout
也不会报错。
`setInterval` 的堆调用(Stacking calls with `setInterval`)
setTimeout
只会执行回调函数一次,不过setInterval
-
正如名字建议的 - 会每隔X
毫秒执行函数一次。但是却不鼓励使用这个函数。
当回调函数的执行被阻塞时,setInterval
仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。
function foo(){
// 阻塞执行 1 秒
}
setInterval(foo, 100);
上面代码中,foo
会执行一次随后被阻塞了一分钟。
在foo
被阻塞的时候,setInterval
仍然在组织将来对回调函数的调用。因此,当第一次foo
函数调用结束时,已经有10次函数调用在等待执行。
处理可能的阻塞调用(Dealing with possible blocking code)
最简单也是最容易控制的方案,是在回调函数内部使用setTimeout
函数。
function foo(){
// 阻塞执行 1 秒
setTimeout(foo, 100);
}
foo();
这样不仅封装了setTimeout
回调函数,而且阻止了调用指令的堆积,可以有更多的控制。foo
函数现在可以控制是否继续执行还是终止执行。
手工清空定时器(Manually clearing timeouts)
可以通过将定时时产生的 ID 标识传递给clearTimeout
或者clearInterval
函数来清除定时,至于使用哪个函数取决于调用的时候使用的是setTimeout
还是setInterval
。
var id = setTimeout(foo, 1000);
clearTimeout(id);
清除所有定时器(Clearing all timeouts)
由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。
// 清空"所有"的定时器
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
可能还有些定时器不会在上面代码中被清除(译者注:如果定时器调用时返回的 ID 值大于 1000),因此我们可以事先保存所有的定时器
ID,然后一把清除。
隐藏使用 `eval`(Hidden use of `eval`)
setTimeout
和setInterval
也接受第一个参数为字符串的情况。这个特性绝对不要使用,因为它在内部使用了eval
。
注意:由于定时器函数不是 ECMAScript 的标准,如何解析字符串参数在不同的 JavaScript 引擎实现中可能不同。事实上,微软的 JScript 会使用Function
构造函数来代替eval
的使用。
function foo() {
// 将会被调用
}
function bar() {
function foo() {
// 不会被调用
}
setTimeout('foo()', 1000);
}
bar();
由于eval
在这种情况下不是被直接调用,因此传递到setTimeout
的字符串会自全局作用域中执行;因此,上面的回调函数使用的不是定义在bar
作用域中的局部变量foo
。
建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。
function foo(a, b, c) {}
// 不要这样做
setTimeout('foo(1,2, 3)', 1000)
// 可以使用匿名函数完成相同功能
setTimeout(function() {
foo(a, b, c);
}, 1000)
注意:虽然也可以使用这样的语法setTimeout(foo,
1000, a, b, c)
,但是不推荐这么做,因为在使用对象的属性方法时可能会出错。(译者注:这里说的是属性方法内,this
的指向错误)
结论(In conclusion)
绝对不要使用字符串作为setTimeout
或者setInterval
的第一个参数,这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。
另外,应该避免使用setInterval
,因为它的定时执行不会被 JavaScript 阻塞。
自动分号插入
尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。
JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。
var foo = function() {
} // 解析错误,分号丢失
test()
自动插入分号,解析器重新解析。
var foo = function() {
}; // 没有错误,解析继续
test()
自动的分号插入被认为是 JavaScript 语言最大的设计缺陷之一,因为它能改变代码的行为。
工作原理(How it works)
下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。
(function(window, undefined) {
function test(options) {
log('testing!')
(options.list || []).forEach(function(i) {
})
options.value.test(
'long string to pass here',
'and another long string to pass'
)
return
{
foo: function() {}
}
}
window.test = test
})(window)
(function(window) {
window.someLibrary = {}
})(window)
下面是解析器"猜测"的结果。
(function(window, undefined) {
function test(options) {
// Not inserted, lines got merged
log('testing!')(options.list || []).forEach(function(i) {
}); // <- 插入分号
options.value.test(
'long string to pass here',
'and another long string to pass'
); // <- 插入分号
return; // <- 插入分号, 改变了 return 表达式的行为
{ // 作为一个代码段处理
// a label and a single expression statement
foo: function() {}
}; // <- 插入分号
}
window.test = test; // <- 插入分号
// The lines got merged again
})(window)(function(window) {
window.someLibrary = {}; // <- 插入分号
})(window); //<- 插入分号
注意:JavaScript 不能正确的处理 return 表达式紧跟换行符的情况,虽然这不能算是自动分号插入的错误,但这确实是一种不希望的副作用。
解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。
前置括号(Leading parenthesis)
在前置括号的情况下,解析器不会自动插入分号。
log('testing!')
(options.list || []).forEach(function(i) {})
上面代码被解析器转换为一行。
log('testing!')(options.list || []).forEach(function(i) {})
log
函数的执行结果极大可能不是函数;这种情况下就会出现TypeError
的错误,详细错误信息可能是undefined
is not a function
。
结论(In conclusion)
建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行,对于只有一行代码的if
或者else
表达式,也不应该省略花括号。这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。
相关推荐
3D-canvas-raycasting.zip,画布 光线投射实验,3D建模使用专门的软件来创建物理对象的数字模型。它是3D计算机图形的一个方面,用于视频游戏,3D打印和VR,以及其他应用程序。
IEEE VIS会议技术大牛关于科学计算可视化中光线投射的体绘制算法的讲义资料,讲解透彻清晰,涉及到体绘制中的高级光照等技术,以及在GPU上着色器代码的实现代码。
本程序实现了体绘制中的光线投射算法,核心代码纯C,只在显示的时候使用了OpenGL。
monogram-casting-源码.rar
The kinetics of recrystallization for twin-roll casting AZ31 magnesium alloy with different thicknesses during homogenization was analyzed. It is shown that fine grains are first formed at the ...
type casting 英文文档
ISO 15854 2023 Dentistry - Casting and baseplate waxes.rar
SnapLensStudio-射线广播 光线投射任何网格以获取位置和旋转。 。 结合使用PinToMesh组件和snap()函数对3D模型进行命中测试。... - Raycasting needs a delay, which is why it cannot return a value instantly
This study first ... Formulating the Anand model in ANSYS software, we then simulated the stress field in the molten pool of type 304 stainless steel during the twin-roll casting process. Parameters
ray-casting 的一个实现 在CPU下完成
3D-ray-casting:表示3D环境中的2D射线投射
var casting = require ( 'casting' ) ; casting . cast ( Array , 123 ) ; // [123] casting . cast ( String , 123 ) ; // '123' casting . cast ( Date , '2014-01-01' ) ; // [object Date] function MyType ( ...
这是Permidi Ray-Casting教程的Python实现 原始文件可以在这里找到: : 关于实现的注意事项为了提高速度,将floorResolution设置为2。这会同时影响地板和天花板,但不会影响全景背景。 要渲染更清晰的地板和天花板...
带有SSDM的Voxel-Terrain射线广播具有像素优化和屏幕空间置换贴图的Voxel Terrain Raycaster 该演示是将Perlin噪声生成地形的地形射线广播与屏幕空间环境光遮挡(SSAO)和屏幕空间位移映射(SSDM)相结合的实验。...
资源来自pypi官网。 资源全名:type_casting-0.4.0.tar.gz
尼克斯射线广播 在编写该项目时,我写下了使用nix找不到的所有东西,这会让我的生活更轻松。 一些浮点函数(floor,ceil,round) 将数字写为十六进制( 0x42而不是66 ) 按位移位 战俘功能 ...
利用Opengl实现的Ray Casting 光线投射算法,并含有加速,对于新手理解该算法有很好的帮助
使用真实标记的体素射线广播 使用6个高度贴图(+ -XYZ)定义对象来渲染体素体积的实验代码。 在下图中,每个佛像都由3个四边形渲染,其中在像素着色器中对每个四边形像素执行光线投射。 您可以在找到有关真正...
GPU ray tracing:这是一个用GPU实现光线追踪的源代码
StarCraft Casting Tool (SCC工具)是一个免费的开放源代码程序,通过提供匹配捕获器,预定义的自定义格式以及要呈现给用户的各种动画图标和浏览器源,可以轻松铸造StarCraft 2,同时大幅提高生产价值。观众。 重要...