JavaScript中怪异的地方


发布者 1518409521  发布时间 1408807630369
关键字 JS学习  JavaScript 

经历语言奇怪特性的旅程


在这篇文章中我想总结一下我们在1月YYCJS聚会讨论的一些事情。这都是关于JavaScript的怪异的部分。你可以在Youtube上找到这个视频, 在yycjs.com/the-weird-parts 找到一些幻灯片,在 JSBin 找到在线编码的部分。


非常感谢@alexisabril的完整性检查, Assembly提供图像。另外,感谢审查这个Minko Gechev的博客(同名)谈论了一些其他的怪异的地方。


热身


进入我们所谈论的内容。我们可以使用(点)或[](方括号)操作符访问对象和对象属性,点操作符只接受有效的JavaScript变量名而方括号可以采用任何字符串:

var person = {
 name: 'david',
 '&weird property': 'YYCJS'
}

var prop = 'name';

person.name // -> David
person['name'] // -> David
person["name"] // -> David
person[prop] // -> David

person['&weird property'] // -> YYCJS

// ERROR
person.&weird property

给字符串使用单引号或双引号没有真正区别。保持一致是有意义,大家似乎普遍偏好单引号(在大多数键盘上少按一些键)。


函数实参


在一个函数中, arguments是一个特殊的类数组变量,它包含所有传递给它的参数:

function sum() {
  var sum = 0;
  for(var i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
}
sum(1, 2, 3, 4); // -> 10

一个函数通过不同数量的参数调用时,arguments通常用于模拟函数重载。

arguments不是一个真正的数组,它只是一个类数组对象拥有长度属性和索引。如果你想使用数组方法如push或pop,您可以使用jQuery.toArray()或这样的来转换他:

var args = Array.prototype.slice.call(arguments, 0);


真值和假值

在JavaScript中, 在一个逻辑表达式中除了实际的布尔值可以评估为真(真值)或假(falsy)。下列值将被视为falsy:

false
null
undefined
0
'' // empty string
person.undefinedPropertys
例如, 他们可以在一个if语句中判断
var person = { name: 'David' }
if(person.name) {
 // Do stuff if property exists and is not falsy
}

注意:这并不适用于未声明的变量名。如果你想看看一个变量是否存在(而不是没有定义),使用typeof运算符:

if(typeof  myvar !== 'undefined') {}

一个逻辑表达式的结果并不总是真实的布尔值。使用| |(或)将返回第一个真值或表达式的最后一个值。这通常用于将默认值分配给一个对象或一个函数的默认参数:

person.meetup = person.meetup || defaults.meetup || 'YYCJS';
function sum(first, second) {
 first = first || 0;
 second = second || 0;
 
 return first + second;
}
sum() // -> 0 instead of NaN
sum(1) // -> 1 instead of NaN

注:我建议重新分配函数参数变量设置默认值, 如果它在任何其他方式将改变请使用局部变量。


等式

真值和假值是等式比较。基本的= =(等于)和!=(不等于)操作符只有比较值(不比较类型),例如:

1 == 1 // -> true
1 == ‘1' // -> true
1 == 2 // -> false

// 下面的就奇怪了
'' == false // -> true
[] == false // -> true, [] not falsy though
null == undefined // -> true

= = =(三重等于)和!= =(三重不等于?)操作符比较值和类型:

1 === 1 // -> true
1 === '1' // -> false
1 === parseInt('1') // -> true
[] === false // -> false
null === undefined // -> false
对象总是比较他们的引用,所以如果它实际上是相同的对象,这些表达式只会是真。
var person = { name: 'David' };
var otherPerson = person;
person === { name: 'David' } // -> false, created new object
person === otherPerson // -> true, same reference
为达到一致性和避免遇到很麻烦的错误总是使用= = =和! = =。有时这可能意味着编写更多的代码 (如转换字符串如果你期望得到一个数字),但它会让事情更容易和更可预测。

范围

JavaScript只知道函数范围。这意味着块或for和while循环不引入一个新的变量范围,有效地让它和在这个函数开始的地方声明每个变量一样。
函数里的其他函数可以访问他们父范围(关闭)的变量而其他的方式不能访问。
var x = 'outer';
function count() {
  for(var i = 0; i < 10; i++) {
    var x = 'testing';
    var inner = function() {
      var i = 42;
      // -> x === 'testing'
      // -> i === 42
    }
    inner();
    // -> i === current count   } } // -> x === 'outer'
有时你会看到所有的变量被声明在函数的开始部分。这主要是一个风格的问题,不是必须的,如果变量名只是描述性的或者函数很小。

闭包

让我们看看下面的jQuery代码片段,这段代码创建十个按钮,添加一个单击处理器并将它们附加到body。
for(var i = 0; i < 10; i++) {
  var button = $('<button>Button #' + i + '</button>');
  button.click(function() {
    alert(i);
  });
  $('body').append(button);
}

然而点击任何按钮不会按你期望的弹出这个按钮的数字,而是每个按钮都弹出数字10。
原因仍然是变量作用域。单击处理函数从他的父作用域访问相同的变量,因为它总是在循环完成后执行,它不管在哪都将具有相同的值。
这里的窍门是通过包装器函数引入一个新的范围,用当前计数调用它。它将被复制到一个新的变量计数器:

for(var i = 0; i < 10; i++) {
  var button = $('<button>Button #' + i + '</button>');
  var wrapper = function(counter) {
    button.click(function() {
      alert(counter);
    });
   }
  wrapper(i);   $('body').append(button); }
只有原始值(字符串、布尔值、数字)将被复制。如果你传递一个对象,你会得到同样的对象引用,重要的是要知道你是否打算修改该对象。

全局变量

在浏览器环境中,没有var声明的变量将自动成为全局性的,在Node中将成为模块的全局变量:
function test() {
 var local = 42;
 global = 'global';
}
test();
这可能是总是无意的。在Node和新生浏览器中避免意外的全局变量泄漏的好方法是使ECMAScript 5严格模式可用,将:

'use strict';

用在一个函数或JavaScript文件的开头。一个全局变量泄漏将抛出一个ReferenceError引用错误。
如果你需要全局变量, 可以明确地通过这个全局对象(在浏览器中是Window,在Node中是global)添加和访问它们:
function test() {
  var local = 42;
  window.global = 'global';
}
test();

this是什么?

在任何函数,this是一个保留字,指的是函数的所有者。例如当调用一个定义在一个对象中的函数,它将是对象本身:
var person = {
 name: 'David',
 sayHi: function() {
  return 'Hello ' + this.name;
 }
}
person.sayHi(); // -> 'Hello David'

this也用在事件处理程序函数中,它指触发这个事件的一个或多个元素:

document.getElementById('mybutton').addEventListener('click',   
  function() {
   this.innerHTML = 'Button clicked';
  });

三位一体的this

能够改变一个函数的所有者可能会导致一些混淆,this当前指的到底是什么,但只有三个规则可以说明this指什么:
1.     对象,当函数在这个对象里被调用(使用“.”或[ ]操作符)
2.  新创建的对象,当一个函数被new操作符创建的对象调用
3.  被调用者设置为call,apply或bind

var person = {

 name: 'David',

 sayHi: function() {

   return 'Hello ' + this.name;

 }

};



function Dog(name) {
 this.name = name;
}

var otherPerson = { name: 'Eric' };

// 1) The object, when the function is called on an object
person.sayHi(); // -> 'Hello David'
person['sayHi'](); // -> 'Hello David'
// 2) The new object when a function is called with new
var goofy = new Dog('Goofy');

goofy.name // -> 'Goofy'


// 3) The owner has been set with call, apply or bind
person.sayHi.call(otherPerson); // -> 'Hello Eric'

var hi = person.sayHi.bind(otherPerson);

hi(); // Hello Eric
否则this在严格模式下将是全局或未定义的对象:

var ownerless = person.sayHi;
ownerless() // "Hello " (or window.name or ReferenceError)

this
callback

回调用于任何类型的异步操作。一个常见的陷阱是忘记在任何新的回调过程中你可能会失去原来的this。保持引用您将需要将其存储在一个新的变量里,这样它就可以被引用(通过闭包):

$('button').click(function() {
 // Store the old this reference (the clicked button)
 var self = this;
 $.getJSON('someFile.json', function(data) {
   // Set the button content
   $(self).html(data.text);
 });
});

总结

这绝对是一个不完整的JavaScript缺陷列表,尤其是刚开始使用JavaScript你可能会被很多事情绊倒。在后续的帖子我想谈谈JavaScript中的继承 (这也会发布在meetup上)。 

 

 





回复 (2)
  • #
  • #1 潘级仰 1477621002469

    还不错,支持一下

  • #2 沈厌同 1504442334452

    在这里输入代码

微信扫码 立即评论