JavaScript中的各种奇葩问题

原文:JavaScript中的各种奇葩问题

JavaScript浮点数

var a = (0.1 + 0.2) + 0.3;
var b = 0.1 + (0.2 + 0.3);
console.log(a);//0.6000000000000001
console.log(b);//0.6

在JavaScript中是没有整数的,所有数字都是双精度浮点数。

尽管JavaScript中缺少明显的整数类型,但是可以进行整数运算。

位算术运算符不会将操作数作为浮点数进行运算,而是会将其隐匿转换为32位整数后进行运算。

尽可能的采用整数值运算,因为整数在表示时不需要舍入。

上述代码最好用如下代码替换

var a = ((10 + 20) + 30)/100;
var b = (10 + (20 + 30))/100;
console.log(a);//0.6
console.log(b);//0.6

当心强制类型转换

var obj = {
    toString: function () {
        return "[object MyObject]";
    },
    valueOf: function () {
        return 17;
    }
};
console.log(obj.valueOf);

也许你会想当然的认为结果是17,可是结果却如下图所示,是不是让你大失所望。原因是对象通过valueOf方法强制转换为数字,通过toString方法强制转换为字符串。

像写如下代码,你本来的原意是想如果用户没有传入x,y,则设置默认值为320,240,结果当你x,y分别传入0,0的时候,函数也将x,y设置为了320,240

function point(x,y) {
    if (!x) {
        x = 320;
    }
    if (!y) {
        y = 240;
    }
    return { x: x, y: y };
}

因为在JavaScript中有7个假值:false 、0、-0、""、 NaN、 null、 undefined,所以当你传入0的时候就自动转换为false了......

function point(x, y) {
    if (typeof x===‘undefined‘) {
        x = 320;
    }
    if (typeof y===‘undefined‘) {
        y = 240;
    }
    return { x: x, y: y };
}

再来看一个例子。

"1.0e0" == {
    valueOf: function () {
        return true;
    }
}//true

这也就是为什么在JS里面尽量使用全等运算符===而不要使用==,除非你了解如下规则。

尽量使用原始类型而不用封闭对象

var s = new String(‘hello‘);
console.log(typeof ‘hello‘);//string
console.log(typeof s);//object

JavaScript有5个原始值类型:布尔值、数字、字符串、null和undefined

String对象是一个真正的对象,它不同于原始的字符串

var s1 = new String(‘hello‘);
var s2 = new String(‘hello‘);
console.log(s1 === s2);//false
console.log(s1 == s2);//false

由于String对象都是一个单独的对象,其总是只等于自身,对于非严格相等运算结果同样如此。

当做相等比较时,原始类型的封装对象与其原始值行为不一样。

命名函数相关问题

var f = function g() {
    return 17;
}

alert(g());

上述代码在ie6执行,弹出17,而在谷歌下则直接报错。这是因为JavaScript环境把f和g这两个函数作为不同的对象,从而导致不必要的内存分配。

所以上述代码在ie6下面最好写成如下所示

var f = function g() {
    return 17;
}
var g = null;
alert(g());

看一下下面代码

        function f() {
            return ‘global‘;
        }
        function test(x) {
            function f() {
                return ‘local‘;
            }
            var result = [];
            if (x) {
                result.push(f());
            }
            result.push(f());
            return result;
        }
        console.log(test(true));//[local,local]
        console.log(test(false));//[local]

也许这样的代码你一眼就能看出答案,那下面的代码呢,你试着猜下结果。

     function f() {
            return ‘global‘;
        }
        function test(x) {

            var result = [];
            if (x) {
                function f() {
                    return ‘local‘;
                }
                result.push(f());
            }
            result.push(f());
            return result;
        }
        alert(test(true));//[local,local]
        alert(test(false));//[local]

ES5建议将非标准环境的函数声明转变成警告或错误,编写可移植的函数的最好方式是始终避免将函数声明置于局部块或子语句中。

Eval

eval最令人吐血的地方就是干扰作用域。也就是说eval函数具有访问调用它那时的整个作用域的能力。

        var y = ‘global‘;
        function test(x) {
            if (x) {
                eval(‘var y="local";‘);
            }
            return y;
        }
        console.log(test(true));//local
        console.log(test(false));//global

那怎样保证eval函数不影响外部作用域呢,那就是匿名函数立即执行。如下所示

var y = ‘global‘;
function test(x) {
    (function (src) {
        eval(src);
    })();
    return y;
}
console.log(test(‘var y="local";‘));//global
console.log(test(‘var z="local";‘));//global

事实上,我们可以绑定eval函数到另一个变量名,通过该变量名调用函数会使代码失去对所有局部作用域的访问能力。

        var y = "global";
        function test(x) {
            var x = "var y=‘local‘";
            var f = eval;
            return f(x);
        }
        console.log(test());//undefnied

这个答案undefined我想应该是你想不到的吧。

将eval函数同一个字面量包裹在序列表达式中以达到强制使用间接调用eval函数的目的。其实我们最常用的间接调用eval的方式是如下所示

(0,eval)(src);

函数相关的巧用

var names = [‘Fred‘, ‘Wilma‘, ‘Pebbles‘];
var upper = [];
for (var i = 0, n = names.length; i < n; i++) {
    upper[i] = names[i].toUpperCase();
}
console.log(upper);

像上面代码,我们要实现将数组中每个项都强制转换成大写,有什么更好的方面吗?

var names1 = [‘Fred‘, ‘Wilma‘, ‘Pebbles‘];
var upper1 = names1.map(function (name) {
    return name.toUpperCase();
});
console.log(upper);

这个代码是否比上面要简单很多,事实上ES5提供了很多类似map的函数,如every,some,forEach等等

var aIndex = ‘a‘.charCodeAt(0);
var alphabet = ‘‘;
for (var i = 0; i < 26; i++) {
    alphabet += String.fromCharCode(aIndex + i);
}
console.log(alphabet);

var digits = ‘‘;
for (var i = 0; i < 10; i++) {
    digits += i;
}
console.log(digits);

var random = ‘‘;
for (var i = 0; i < 8; i++) {
    random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
}
console.log(random);

像上面三个方面的逻辑当中都有类似的部分,大家都知道程序的坏味道就是重复相同的代码,那怎样让代码更加简单。将相似的逻辑封装成方法,然后。。。

function buildString(n, callback) {
    var result = ‘‘;
    for (var i = 0; i < n; i++) {
        result += callback(i);
    }
    return result;
}
var alphabet1 = buildString(26, function (i) {
    return String.fromCharCode(aIndex + i);
});

console.log(alphabet1);

var digits1 = buildString(10, function (i) {
    return i;
})
console.log(digits1);

var random1 = buildString(8, function () {
    return String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
})
console.log(random1);

是不是简单了很多。

arguments的一些异样。。。

先从例子说起吧,下面的方法原意是想得到两数相加的结果,可是却报错了

function callMethod(obj,method) {
    var shift = [].shift;
    shift.call(arguments);
    shift.call(arguments);
    return obj[method].apply(obj, arguments);
}

var obj = {
    add: function (x, y) {
        return x + y;
    }
};
console.log(callMethod(obj, ‘add‘, 17, 25));

那么我们应该如何修改才能达到相加的效果呢?事实上很简单

function callMethod(obj, method) {
    var args = [].slice.call(arguments, 2);
    return obj[method].apply(obj, args);
}
var obj = {
    add: function (x, y) {
        return x + y;
    }
};
console.log(callMethod(obj, ‘add‘, 17, 25));//42

这个例子说明了什么呢,也就是说我们不要随意修改arguments的值。[].slice.call(arguments)将arguments对象复制到一个真正的数组中再进行修改。

使用事实上在严格模式下,函数参数不支持对arguments修改。

function strict(x) {
    ‘use strict‘;
    arguments[0] === ‘modified‘;
    return x === arguments[0];
}

function nonstrict(x) {
    arguments[0] === ‘modified‘;
    return x === arguments[0];
}
console.log(strict(‘unmodified‘))//true
console.log(nonstrict(‘unmodified‘));//true

再看一个相关的例子

function values() {
    var i = 0, n = arguments.length;

    return {
        hasNext: function () {
            return i < n;
        },
        next: function () {
            if (i >= n) {
                throw new Error(‘end of iteration‘);
            }
            return arguments[i++];
        }
    };
}
var it = values(1, 3, 4, 5, 2, 4, 6, 7, 8, 3, 45);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

本来我们期望是得到1,3,4,5,结果却发现每个值都是undefined,原因在next函数中的arguments和values函数中的arguments不是一个对象,所以一定要当心函数嵌套层级问题,

那我们应该如何改正问题呢

function values() {
    var i = 0, n = arguments.length, a = arguments;

    return {
        hasNext: function () {
            return i < n;
        },
        next: function () {
            if (i >= n) {
                throw new Error(‘end of iteration‘);
            }
            return a[i++];
        }
    };
}
var it = values(1, 3, 4, 5, 2, 4, 6, 7, 8, 3, 45);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

也就是说我们最好使用临时变量来保存中间值。

当心函数的接收者

var buffer = {
    entries: [],
    add: function (s) {
        this.entries.push(s);
    },
    concat: function () {
        return this.entries.join(‘‘);
    }
};

var source = [‘897‘, ‘_‘, ‘8579‘];
console.log(source.forEach(buffer.add));//这种是错误的  也许你期望着能得到897_8579  却发现报错了

事实上这个问题就是this导致的,也就是说在提取一个方法的时候不会将方法的接收者绑定到该方法的对象上。那如何解决呢,有好些方法呢,看看吧!

var buffer = {
    entries: [],
    add: function (s) {
        this.entries.push(s);
    },
    concat: function () {
        return this.entries.join(‘‘);
    }
};

var source = [‘897‘, ‘_‘, ‘8579‘];
//console.log(source.forEach(buffer.add));//这种是错误的  也许你期望着能得到897_8579  却发现报错了
source.forEach(buffer.add, buffer);//这个方法就是说我们把this对象的接收者给传进去
source.forEach(function (s) {//也可以这样  在函数方法里面在适应的接收者上调用该方法...
    buffer.add(s);
});

source.forEach(buffer.add.bind(buffer));//也可以使用bind方法来指定对象的接收者
console.log(buffer.add === buffer.add.bind(buffer));//再来看看这句代码  返回true  我相信你应该悟出了点什么吧

使用闭包而不是字符串来封装代码

function repeat(n, action) {
    for (var i = 0; i < n; i++) {
        eval(action);
    }
}
function f() {
    var a = 0, b = 1, n = 100, sum = 0;
    for (var i = 0; i < n; i++) {
        sum = a + b;
        a = b;
        b = a + b;
    }
}
var start = [],
    end = [],
    timings = [];
repeat(1000, "start.push(Date.now());f();end.push(Date.now());");

上面的代码就是实现计时的功能。你或许不知道我在说什么,但是你接着往下看。

/*
*@desc:这样写就报错了,大家知道原因吗???
*/function benchmark() {
    var start = [],
    end = [],
    timings = [];
    repeat(1000, "start.push(Date.now());f();end.push(Date.now());");

    for (var i = 0, n = start.length; i < n; i++) {
        timings[i] = end[i] - start[i];

    }
    return timings[i];
}

上述代码报错了,你知道为什么我仅仅移动了一下位置,只是把代码移动到一个函数中就导致报错的原因了吗?因为这时的start只是benchmark函数内的局部变量,而eval执行时是调用的全局变量start.

那怎么样让代码正常执行不报错而又能起到封装效果呢?用下面的代码试试吧!

/*
*@desc:还是这样写比较靠谱
*/
function benchmark() {
    var start = [],
    end = [],
    timings = [];
    repeat(1000, function () {
        start.push(Date.now()); f(); end.push(Date.now());
    });

    for (var i = 0, n = start.length; i < n; i++) {
        timings[i] = end[i] - start[i];

    }
    return timings;
}
console.log(benchmark());;

暂时先写这些吧!其实还有好多,文笔不好,写的不够容易理解,望见谅

时间: 2024-11-08 22:24:40

JavaScript中的各种奇葩问题的相关文章

JavaScript语法对{}的奇葩处理

JavaScript的语法有多坑,算是众人皆知了. 今天看到vczh的这条微博:http://weibo.com/1916825084/B7qUFpOKb , 代码如下: {} + []; // 0 [] + {}; // "[object Object]" {} + [] == [] + {}; // false ({} + [] == [] + {}); // true 这么蛋疼的语法坑估计也只有 JavaScript 这样的奇葩才有. 相信对于绝大部分不研究 JavaScript

JavaScript中判断为整数的多种方式

原文:JavaScript中判断为整数的多种方式 之前记录过JavaScript中判断为数字类型的多种方式,这篇看看如何判断为整数类型(Integer). JavaScript中不区分整数和浮点数,所有数字内部都采用64位浮点格式表示.但实际操作中比如数组索引.位操作则是基于32位整数. 方式一.使用取余运算符判断 任何整数都会被1整除,即余数是0.利用这个规则来判断是否是整数. function isInteger(obj) { return obj%1 === 0 } isInteger(3

javascript中外部js文件取得自身完整路径得办法

原文:javascript中外部js文件取得自身完整路径得办法 有时候我们需要引入一个外部js文件,这个js文件又需要用到自己的路径或者是所在的目录,别问怎么又这么变态的需求,开发做久了各种奇葩需求也就有了! 有人第一时间想到的是location.href,可是哥哥,那个引用页面的路径啊.比如a.html: <html> <script src="/b/c.js"></script> </html> 这样的话我们用location.hre

source insight——编码工具中的一朵奇葩

source insight是一款非常优秀的源代码编辑/浏览软件.本文从以下几个方面随便说说他的优秀之处. 逻辑上可能有点乱,表述上也可能有不准确的地方.另外,也难以将source insight的好处说全. 一.圆满的设计+圆满的实现 这是对此软件的总体评论. 从source insight官方网站(http://www.sourceinsight.com/)上可以看到, 目前此软件的最新版本是 3.5.0072,编译日期为2013年3月19日.至此,3.5版本至少已经维护了10年以上. 在十

深入理解javascript中的立即执行函数(function(){…})()

这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是用(function(){-})()包住业务代码,使用jquery时比较常见,需要的朋友可以参考下http://www.jb51.net/article/50967.htm javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解. ( function()

javascript中的数字玩法,颠覆你的眼睛

1.JavaScript中的数字中有一些很奇葩的现象. 在Chrome控制台中可以自己做一下实验: 1 === 1.0 ; //true 习惯了强类型语言,如java,c,OC看到这个结论还是有点小迷茫的.这是因为JavaScript内部,所有数字都是以64位浮点数形式存储的,包括正数.它遵循IEEE 754标准. 再看,浮点数的计算. 0.1 + 0.2: 浮点数 发现,0.3 - 0.2 不等于 0.2 - 0.1.所以在做浮点数计算和比较的时候还是要小心的. 对于整数运算,JavaScri

【转】深入理解javascript中的立即执行函数(function(){…})()

javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解. ( function(){…} )()和( function (){…} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此.要理解立即执行函数,需要先理解一些函数的基本概念.

实现一个函数clone,使JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制

实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制. 1 /** 对象克隆 2 * 支持基本数据类型及对象 3 * 递归方法 */ 4 function clone(obj) { 5 var o; 6 switch (typeof obj) { 7 case "undefined": 8 break; 9 case "string": o = obj + &q

javascript中的原始值和复杂值

前面的话 javascript的数据类型可以分为两种:原始类型(基本类型或者简单类型)和引用类型. 原始类型:Undefined,Null,Boolean,Number,String五种: 引用类型:Object,Array,Function: 与此相对应的,它们的值分别被称为原始值和复杂值. 特性 原始值 原始值是表示javascript中可用的数据或信息的最底层的形式或者最简单的形式.原始类型的值被称为原始值,因为它们的值是不可被细化的.也就是说,数字是数字,字符串是字符串,布尔值是true