JavaScript 代码片段分析

Posted by violetks on April 6, 2023

一、setTimeout 输出 10 个 10

for (var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

分析:涉及到 JavaScript 的执行机制,JavaScript 是一个单线程的解释器,setTimeout是异步执行函数,本质是间隔一定时间将任务添加到任务队列(Event Queue)中。for循环作为主线程先执行完毕,按序执行 10 次输出i,就会输出 10 个 10。

如何实现按序输出?

方法一:闭包

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i);
    }, 1000);
  })(i);
}

方法二:ES6 的let声明变量

for (let i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

方法三:使用setTimeout的第三个参数

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000, i);
}

二、意外全局变量

function foo () {
  let a = b = 0;
  a++;
  return a;
}

foo();
typeof a;  // => ???
typeof b;  // => ???

分析:let a = b = 0;该语句声明一个局部变量a,以及全局变量b
foo()范围或全局范围中都没有声明变量b。因此 JavaScript 将b = 0解释为window.b = 0
在浏览器中,以上代码片段等效于:

function foo () {
  let a;
  window.b = 0;
  a = window.b;
  a++;
  return a;
}

foo();
typeof a;  // => 'undefined'
typeof b;  // => 'number'

typeof a等于'undefined',变量a存在于foo()范围内,而在外部范围内不使用。
b是一个值为 0 的全局变量。

三、数组的 length 属性

const clothes = ['jacket','t-shirt'];
clothes.length = 0;

clothes[0];  // => ???

分析:减少 length 属性的值的结果是删除自己的数组元素。因此,clothes[0]等于undefined,因为clothes数组已被清空。

四、鹰眼测试

// numbers 数组的内容是什么?
const length  = 4;
const numbers = [];
for (var i = 0; i < length; i++);{
  numbers.push(i + 1);
}

numbers; // => ???

分析:让我们仔细看一下分号; 出现在左大括号{之前。这个分号创建了一个空语句。上面的代码等效于以下代码:

const length = 4;
const numbers = [];
var i;
for (i = 0; i < length; i++) {
  // does nothing
}
{
  // a simple block
  numbers.push(i + 1);
}

numbers; // => [5]

for()i变量递增到 4,然后 JavaScript 一次进入块{numbers.push(i + 1);},将4 + 1推入数字数组。 因此,numbers数组的内容为[5]

五、自动分号插入

// arrayFromValue() 返回什么值?
function arrayFromValue (item) {
  return
    [item];
}

arrayFromValue(10); // => ???

分析:很容易错过return关键字和[item]表达式之间的换行符。此换行符使 JavaScript 自动在return[item]表达式之间插入分号。 return;函数内部使其返回undefined。因此arrayFromValue(10)的值为undefined

六、浮点数计算

0.1 + 0.2 === 0.3 // => ???

分析:浮点数计算会产生误差,并不等于0.3,结果是false

七、变量提升

myVar;   // => ???
myConst; // => ???

var myVar = 'value';
const myConst = 3.14;

分析:在声明之前访问myVar的结果为undefined。在初始化之前,提升的var变量具有undefined的值。
然而,在声明行之前访问myConst会引发ReferenceErrorconst变量处于临时死区,直到声明行const myConst = 3.14

八、下列表达式结果

""==0      // true,0 和空字符串都是 false,值相等
{}==={}    // false,引用数据类型,是两个独立的对象
0===0      // true
!""        // true
[]==![]    // true,相当于 []==false,类型转换 0==0,所以是 true
1+'1'      // 11

九、逻辑运算符

var a=1,b=true,c=2,d=3;
a = b && d && c;
console.log(a);    // 2

a && b:如果 a 是 false,那么 b 不管是 true 还是 false,都返回 false,因此不用判断 b 了,这个时候刚好判断到 a,因此返回 a。
    如果 a 是 true,那么就要再判断 b,和刚刚一样,不管 b 是 true 是 false,都返回 b。
a || b:如果 a 是 true,那么 b 不管是 true 还是 false,都返回 true。因此不用判断 b 了,这个时候刚好判断到 a,因此返回 a。
    如果 a 是 false,那么就要判断 b,如果 b 是 true,那么返回 true,如果 b 是 false,返回 false,其实不就是返回 b 了吗。

十、运算符优先级

// 假设 val 已经声明,可定义为任何值
console.log('Value is ' + (val != '0') ? 'define' : 'undefine');  // => ???

分析:加号优先级高于三目运算,低于括号。所以括号中无论真假,加上前边的字符串都为 true,三目运算为 true 是输出 define

十一、遍历数组 arr,剔除数组中为 0 的元素,最终会被剔除掉几个 0

var arr = [0,0,2,0,0,4,0,1,0,0,0,2];
for (var i = 0; i < arr.length; i++) {
  if (arr[i] == 0)
  arr.splice(i, 1);
}

分析:splice()方法会改变原始数组。
第一次:i=0,arr[0]为 0,删除,数组[0,2,0,0,4,0,1,0,0,0,2]
第二次:i=1,arr[1]为 2,不删除。
第三次:i=2,arr[2]为 0,删除,数组[0,2,0,4,0,1,0,0,0,2]
第四次:i=3,arr[3]为 4,不删除。
第五次:i=4,arr[4]为 0,删除,数组[0,2,0,4,1,0,0,0,2]
第六次:i=5,arr[5]为 0,删除,数组[0,2,0,4,1,0,0,2]
第七次:i=6,arr[6]为 0,删除,数组[0,2,0,4,1,0,2]
第八次:i=7,arr[7]不存在,结束循环。
总共删除了 5 个 0。

// 解决:删除所有 0
var arr = [0,0,2,0,0,4,0,1,0,0,0,2];
for (var i = 0; i < arr.length; i++) {
  if (arr[i] == 0)
  arr.splice(i--, 1);
}

十二、有函数 F,以下 call 及 apply 方法写法错误的是

A: F.apply(this, "")
B: F.apply(undefined, [])
C: F.call(null, [])
D: F.call(null, null)

正确答案:A
分析:apply()方法需要以数组形式一次性传入所有调用函数。
call()方法必须详细列出每个参数,C 选项中只传了一个参数,只不过是数组形式。

十三、toString()

3.toString()     // => ???
3..toString()    // => ???
3...toString()   // => ???

分析:因为小数点是数字的有效部分,所以第一个点被认为是数字,第二个点是链接。得到的结果分别是ERROR3ERROR
数字转换为字符串:toString()或者String()

num.toString();
num.toString(2)  // 二进制

(1)nullundefined转换需要用String()
(2)对于NumberBooleanString()toString()相同

十四、[“1”,”2”,”3”].map(parseInt)

(1)map()方法:按照原始数组元素顺序依次处理元素,返回一个新数组。
(2)parseInt(string, radix)

string:必须要被解析的字符串
radix:可选表示要解析的数字基数

当忽略参数radix,默认数字的基数如下:

  • 如果string以 “0x” 开头,parseInt()会把 string 的其余部分解析为十六进制的整数。
  • 如果string以 0 开头,那么 ECMAScript v3 允许parseInt()的一个实现把其后的字符解析为八进制或十六进制的数字。
  • 如果string以 1 ~ 9 的数字开头,parseInt()将把它解析为十进制的整数。
parseInt(1, 0);  //将十进制数 1 转化为十进制数,仍为 1
parseInt(2, 1);  //将一进制数 2(不存在)转化为十进制数,NaN
parseInt(3, 2);  //NaN
parseInt(4, 3);  //NaN
parseInt(10, 4); //将四进制数 10 转化为十进制数为 4

arr.map(parseInt)含义为对数组arr的每一项调用parseInt()方法,传入的参数为每一项的值和该值的索引。

["1", "2", "3"].map(parseInt)  等价于
[parseInt("1", 0), parseInt("2", 1), parseInt("3", 2)]

结果为:[1,NaN,NaN]