JavaScript重难点实例精讲

map()函数与parseInt()函数的隐形坑

  1. 例子:存在一个数组,数组中的每个元素都是Number类型的字符串[‘1’,‘2’, ‘3’, ‘4’],如果我们想要将数组中的元素全部转换为整数,应该怎么做?

我们可能会想到在Array的map()函数中调用parseInt()函数。像下面这样:

var arr = ['1', '2', '3', '4'];
var result = arr.map(parseInt);
console.log(result);

但是在运行后,得到的结果是[1, NaN, NaN, NaN],与我们期望的结果[1, 2, 3, 4]差别很大,其实这就是一个藏在map()函数与parseInt()函数中的隐形坑。

arr.map(parseInt)

上面的代码实际与下面的代码等效:

arr.map(function (val, index) {
    return parseInt(val, index);
});

parseInt()函数接收的第二个参数实际为数组的索引值,所以实际处理的过程如下所示。

parseInt('1', 0);  // 1
parseInt('2', 1);  // NaN
parseInt('3', 2);  // NaN
parseInt('4', 3);  // NaN

任何整数以0为基数取整时,都会返回本身,所以第一行代码会返回“1”。

第二行代码parseInt(‘2’, 1),因为parseInt()函数对应的基数只能为2~36,不满足基数的整数在处理后会返回“NaN”;

第三行代码parseInt(‘3’, 2),表示的是将3处理为二进制表示,实际上二进制时只有0和1,3超出了二进制的表示范围,无法转换,返回“NaN”;

第四行代码parseInt(‘4’, 3),与第三行类似,4无法用三进制的数据表示,返回“NaN”。

因此我们在map()函数中使用parseInt()函数时需要注意这一点,不能直接将parseInt()函数作为map()函数的参数,而是需要在map()函数的回调函数中使用,并尽量指定基数,代码如下所示。

var arr = ['1', '2', '3', '4'];

var result = arr.map(function (val) {  
    return parseInt(val, 10);
});

console.log(result);  // [1, 2, 3, 4]

isNaN()函数与Number.isNaN()函数

Number类型数据中存在一个比较特殊的数值NaN(Not a Number),它表示应该返回数值却并未返回数值的情况。

NaN存在的目的是在某些异常情况下保证程序的正常执行。例如0/0,在其他语言中,程序会直接抛出异常,而在JavaScript中会返回“NaN”,程序可以正常执行。

NaN的特点:

    1. 任何涉及NaN的操作都会返回“NaN”
    1. NaN与任何值都不相等,包含NaN自身

在判断NaN时,ES5提供了isNaN()函数,ECMAScript 6(后续简称ES6)为Number类型增加了静态函数isNaN()。

既然在ES5中提供了isNaN()函数,为什么要在ES6中专门增加Number.isNaN()函数呢?两者在使用上有什么区别呢?

这是因为isNaN()函数本身存在误导性,而ES6中的Number.isNaN()函数会在真正意义上去判断变量是否为NaN,不会做数据类型转换。只有在传入的值为NaN时,才会返回“true”,传入其他任何类型的值时会返回“false”。

可以通过下面的代码测试下:

isNaN(NaN);       // true
isNaN(undefined);  // true
isNaN({});        // true

isNaN(true);      // false,Number(true)会转换成数字1
isNaN(null);      // false,Number(null)会转换成数字0
isNaN(1);         // false
isNaN('');        // false,Number('')会转换为成数字0
isNaN("1");            // false,字符串"1"可以转换成数字1
isNaN("JavaScript");   // true,字符串"JavaScript"无法转换成数字
// Date类型
isNaN(new Date());     // false
isNaN(new Date().toString());  // true

// Date是一种比较特殊的类型,当我们调用new Date()函数生成的实例并转换为数值类型时,
// 会转换为对应的时间戳,例如下面的代码
Number(new Date()); // 1543333199705
Number.isNaN(NaN);        // true
Number.isNaN(undefined);   // false
Number.isNaN(null);       // false
Number.isNaN(true);       // false
Number.isNaN('');         // false
Number.isNaN(123);        // false

总结:

isNaN()函数与Number.isNaN()函数的差别如下:

  • isNaN()函数在判断是否为NaN时,需要先进行数据类型转换,只有在无法转换为数字时才会返回“true”
  • Number.isNaN()函数在判断是否为NaN时,只需要判断传入的值是否为NaN,并不会进行数据类型转换。

JavaScript常用判空方法

1. 判断变量为空对象
function isEmpty(obj) {
  for(let key in obj) {
      if(obj.hasOwnProperty(key)) {
         return false;
      }
  }
  return true;
}
2. 判断变量为空数组
arr instanceof Array && arr.length === 0
3. 判断变量为空字符串
str == '' || str.trim().length == 0;
4. 判断变量为0或者NaN
!(Number(num) && num) == true;

new操作符到底做了什么?

new操作符在执行过程中会改变this的指向,所以在了解new操作符之前,我们先解释一下this的用法。

function Cat(name, age) {
   this.name = name;
   this.age = age;
}
console.log(new Cat('miaomiao',18));  // Cat {name: "miaomiao", age: 18}

输出的结果中包含了name与age的信息。事实上我们并未通过return返回任何值,为什么输出的信息中会包含name和age属性呢?其中起作用的就是this这个关键字了。

我们通过以下代码输出this,看看this具体的内容。

function Cat(name,age) {
   console.log(this);  // Cat {}
   this.name = name;
   this.age = age;
}
new Cat('miaomiao',18);

我们可以发现this的实际值为Cat空对象,后两句就相当于给Cat对象添加name和age属性,结果真的是这样吗?不如我们改写一下Cat函数。

function Cat(name,age){
   var Cat = {};
   Cat.name = name;
   Cat.age = age;
}
console.log(new Cat('miaomiao',18));  // Cat {}

输出结果中并未包含name和age属性,这是为什么呢?

因为在JavaScript中,如果函数没有return值,则默认return this。而上面代码中的this实际是一个Cat空对象,name和age属性只是被添加到了临时变量Cat中。为了能让输出结果包含name和age属性,我们将临时变量Cat进行return就可以了。

function Cat(name, age) {
   var Cat = {};
   Cat.name = name;
   Cat.age = age;
   return Cat;
}
console.log(new Cat('miaomiao', 18));  // {name: "miaomiao", age: 18}

最后的返回值中包含了name和age属性。通过以上的分析,我们了解了构造函数中this的用法,那么它与new操作符之间有什么关系呢?

我们先来看看下面这行简单的代码,该代码的作用是通过new操作符生成一个Cat对象的实例。

var cat = new Cat();

从表面上看这行代码的主要作用是创建一个Cat对象的实例,并将这个实例值赋予cat变量,cat变量就会包含Cat对象的属性和函数。
其实,new操作符做了3件事情,如下代码所示。

1. var cat = {};
2. cat._ _proto_ _  =  Cat.prototype;
3. Cat.call(cat);

第一行:创建一个空对象。
第二行:将空对象的__proto__属性指向Cat对象的prototype属性。
第三行:将Cat()函数中的this指向cat变量。

于是cat变量就是Cat对象的一个实例。

我们自定义一个类似new功能的函数,来具体讲解上面的3行代码。

function Cat(name, age) {
   this.name = name;
   this.age = age;
}
function New() {
   var obj = {};
   var res = Cat.apply(obj, arguments);
   return typeof res === 'object' ? res : obj;
}
console.log(New('mimi', 18));  //Object {name: "mimi", age: 18}

返回的结果中也包含name和age属性,这就证明了new运算符对this指向的改变。Cat.apply(obj, arguments)调用后Cat对象中的this就指向了obj对象,这样obj对象就具有了name和age属性。

因此,不仅要关注new操作符的函数本身,也要关注它的原型属性。

我们对上面的代码进行改动,在Cat对象的原型上增加一个sayHi()函数,然后通过New()函数返回的对象,去调用sayHi()函数,看看执行情况如何。

function Cat(name, age) {
   this.name = name;
   this.age = age;
}

Cat.prototype.sayHi = function () {
   console.log('hi')
};

function New() {
   var obj = {};
   var res = Cat.apply(obj, arguments);
   return typeof res === 'object' ? res : obj;
}
console.log(New('mimi', 18).sayHi());

运行以上代码得到的结果如下所示:

Uncaught TypeError: New(...).sayHi is not a function

我们发现执行报错了,New()函数返回的对象并没有调用sayHi()函数,这是因为sayHi()函数是属于Cat原型的函数,只有Cat原型链上的对象才能继承sayHi()函数,那么我们该怎么做呢?

这里需要用到的就是__proto__属性,实例的__proto__属性指向的是创建实例对象时,对应的函数的原型。设置obj对象的__proto__值为Cat对象的prototype属性,那么obj对象就继承了Cat原型上的sayHi()函数,这样就可以调用sayHi()函数了。

function Cat(name, age) {
   this.name = name;
   this.age = age;
}

Cat.prototype.sayHi = function () {
   console.log('hi')
};

function New() {
   var obj = {};
   obj._ _proto_ _ = Cat.prototype;  // 核心代码,用于继承
   var res = Cat.apply(obj, arguments);
   return typeof res === 'object' ? res : obj;
}
console.log(New('mimi', 18).sayHi());

结果输出“hi”,方法调用成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值