Nodejs Javascript编程“陷阱”

Nodejs Javascript编程“陷阱”

最近在读一本电子书——Miku’s Node Book。读了几章,看到一些我没注意过的东西,说他是陷阱也可以。

####1.关于this关键字

the value of this is determined at the time the function is called, rather than being fixed to some particular value

首先看一段代码:

var obj = {
  id: "xyz",
  printId: function() {
    console.log('The id is '+ this.id + ' '+ this.toString());
  }
};
obj.printId();//这是第一行输出
var callback=obj.printId;
callback();//这是第二行输出
setTimeout(obj.printId,1000);//这是第三行输出

没读这书之前,很多有其他语言编程经验的人估计都会认为答案是:The id is xyz [object Object],但真实的结果是这样的

第一行输出:The id is xyz [object Object];(正常)
第二行输出:The id is undefined [object global];
第三行输出:The id is undefined [object Object];

这样就出现问题了,只有obj.printId()返回了我们想要的结果,第二中调用只是一个赋值,但是this却指向global,第三种调用,this指向Object,但是显然不是我们定义的obj,因为它获取不到id属性。原因就在于,在javascript中,this关键字的指向实在函数调用的时候定义的。callback()在调用的时候,属于全局顶层的函数,已经不是我们定义的obj中的一个函数,所以this指向global,至于setTimeout的回调函数调用方式之所以指向一个Object,这应该和setTimeout这个api的实现方式有关。

那我们有没有办法用callback和setTimeout方式显示出正常结果呢?你可以这样写:

obj.printId();//这是第一行输出
var callback=function() { obj.printId(); };
callback();//这是第二行输出
setTimeout(function() {obj.printId(); },1000);//这是第三行输出

####2.关于变量作用域

In Javascript, all functions store “a hierarchical chain of all parent variable objects, which are above the current function context; the chain is saved to the function at its creation”.

继续看例子:

第一个,最简单的例子:

for(var i = 0; i < 5; i++) {
  console.log(i);
}

第二个,加个异步函数,也很简单:

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

第三个,我们制作一个方法数组:

var data = [];
for (var i = 0; i < 5; i++) {
 data[i] = function foo() {
   console.log(i);
 };
}
data[0](); data[1](); data[2](); data[3](); data[4]();

第一个,如我们所想,输出0,1,2,3,4;可是第二个第三个都是……5,5,5,5,5.

这个是因为,我们在for循环中定义的变量i,在for循环结束后还没有走出它的作用域,也就是说for循环之后,i依然可以访问,值等于最后一次循环后的值,就是5了。第二段代码中,setTimeout的回调函数执行时,for循环已经完成,此时i就是5。同理,第三段代码,在data[i] ()执行时,i已经是5了。当然,我们有办法让它显示正确结果,但要增加一个变量,像这样:

for(var i = 0; i < 5; i++) {
  (function() {
    var j = i;
    setTimeout( function() { console.log(j); }, 500*i);
  })();
}

####bonus:在object外面增加method的方法

像这样:

var obj1 = { id: "Foo"};
var obj2 = { id: "Bar"};
function f1(a, b) {
  console.log(this, a, b);
}
f1.call(obj1, 'A', 'B');
f1.apply(obj2, [ 'A', 'B' ]);

ps:代码来自 Mixu’s Node Book,你也可以在我的博客都到这篇文章。


6 回复

Node.js JavaScript 编程“陷阱”

最近在读一本电子书——Miku’s Node Book。读了几章,看到了一些我没注意过的东西,说他是陷阱也可以。

1. 关于 this 关键字

在JavaScript中,this 关键字的指向是在函数调用时确定的,而不是在函数声明时固定为某个特定值。让我们通过一段代码来理解这一点:

var obj = {
  id: "xyz",
  printId: function() {
    console.log('The id is ' + this.id + ' ' + this.toString());
  }
};

// 第一种调用方式
obj.printId(); // 这是第一行输出
// 输出:The id is xyz [object Object]

// 第二种调用方式
var callback = obj.printId;
callback(); // 这是第二行输出
// 输出:The id is undefined [object global]

// 第三种调用方式
setTimeout(obj.printId, 1000); // 这是第三行输出
// 输出:The id is undefined [object Object]

这段代码展示了 this 关键字的行为:

  • obj.printId() 中,this 指向 obj
  • callback() 中,this 指向全局对象 global,因为这是一个全局函数调用。
  • setTimeout(obj.printId) 中,this 指向 Window 对象,因为 setTimeout 的回调函数在全局上下文中执行。

如何解决这个问题呢?可以通过以下方式:

obj.printId(); // 这是第一行输出
var callback = function() { obj.printId(); };
callback(); // 这是第二行输出

setTimeout(function() { obj.printId(); }, 1000); // 这是第三行输出

2. 关于变量作用域

在JavaScript中,所有函数都会保存一个指向其父级变量对象的链表。这个链表在函数创建时就已确定。让我们通过几个例子来理解这一点:

// 第一个例子
for (var i = 0; i < 5; i++) {
  console.log(i);
}
// 输出:0, 1, 2, 3, 4
// 第二个例子
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
// 输出:5, 5, 5, 5, 5
// 第三个例子
var data = [];
for (var i = 0; i < 5; i++) {
  data[i] = function foo() {
    console.log(i);
  };
}

data[0](); data[1](); data[2](); data[3](); data[4]();
// 输出:5, 5, 5, 5, 5

这些例子展示了 i 变量的作用域问题。在循环结束后,i 的值仍然是最后一次循环后的值(即5)。因此,在 setTimeout 回调函数执行时,i 已经是5。

如何解决这个问题呢?可以通过立即执行函数表达式(IIFE)来创建一个新的作用域:

for (var i = 0; i < 5; i++) {
  (function() {
    var j = i;
    setTimeout(function() { console.log(j); }, 500 * i);
  })();
}

这样,每个迭代都会创建一个新的作用域,并且 j 将在每个迭代中保持正确的值。

Bonus:在对象外部增加方法的方法

我们可以在对象外部动态地添加方法:

var obj1 = { id: "Foo" };
var obj2 = { id: "Bar" };

function f1(a, b) {
  console.log(this, a, b);
}

// 使用 call 方法绑定 this
f1.call(obj1, 'A', 'B'); // 输出:{ id: "Foo" } A B

// 使用 apply 方法绑定 this
f1.apply(obj2, ['A', 'B']); // 输出:{ id: "Bar" } A B

这些示例展示了JavaScript中常见的陷阱以及如何避免这些问题。希望对你有所帮助!


不错,以后面试就拿这些题目来考别人…

经我以往的经验和再次测试,第3个应该为 The id is undefined [object Window];楼主可以再测试测试enter image description here

我也同意楼上的!! 2、callback()运行的时候,this 应该指向 全局的window 3、setTimeout的执行也是在全局window下面的

看来node里对setTimeout的实现与浏览器不一样啊。凭经验我也觉得会是global之类的,但是node下打印出来this如下。

{ _idleTimeout: 1000,
  _idlePrev: null,
  _idleNext: null,
  _onTimeout: [Function],
  _idleStart: Sun Oct 28 2012 11:43:20 GMT+0800 (CST) }

Nodejs JavaScript 编程“陷阱”

在这篇文章中,我们将讨论一些在 Node.js 和 JavaScript 中常见的陷阱,这些问题常常导致程序出现意外的行为。

1. 关于 this 关键字

在 JavaScript 中,this 的值是在函数被调用时确定的,而不是在定义时固定的。这可能会导致一些意料之外的结果。

var obj = {
  id: "xyz",
  printId: function() {
    console.log('The id is ' + this.id + ' ' + this.toString());
  }
};

obj.printId(); // 输出:The id is xyz [object Object]

var callback = obj.printId;
callback(); // 输出:The id is undefined [object global]

setTimeout(obj.printId, 1000); // 输出:The id is undefined [object Object]

这里只有 obj.printId() 返回了我们想要的结果。对于 callback()this 指向的是全局对象(global),因此 this.idundefined。对于 setTimeoutthis 指向的是全局对象或当前的执行上下文,因此同样输出 undefined

为了解决这个问题,我们可以使用箭头函数或显式绑定 this

obj.printId(); // 输出:The id is xyz [object Object]
var callback = () => obj.printId();
callback(); // 输出:The id is xyz [object Object]

setTimeout(() => obj.printId(), 1000); // 输出:The id is xyz [object Object]

2. 关于变量作用域

JavaScript 中的变量作用域也是一个常见的陷阱。在循环中定义的变量可能会导致意外的结果,因为它们的作用域会超出预期。

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100 * i);
}

// 输出:5 5 5 5 5

这是因为 i 在每次循环结束时都被更新,并且 setTimeout 的回调函数在循环结束后执行,此时 i 的值已经是 5。

为了解决这个问题,我们可以使用立即执行函数表达式(IIFE)来创建一个新的作用域:

for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 100 * j);
  })(i);
}

// 输出:0 1 2 3 4

通过这种方式,每个 setTimeout 回调函数都有自己的 j 变量,不会受到外部循环的影响。

这些陷阱展示了 JavaScript 的一些微妙之处,需要开发者特别注意。

回到顶部