60

对不起noobie问题。你能解释一下,有什么区别:

1. var a = [];
   a['b'] = 1;

2. var a = {};
   a['b'] = 1;

我在互联网上找不到文章,所以写在这里。

4

5 回答 5

204

字面量

和分别称为数组和对象字面量[]{}

var x = []简称var x = new Array();

并且var y = {}是缩写var y = new Object();

数组

数组是具有长度属性的结构。您可以通过它们的数字索引访问值。

var x = [] or var x = new Array();
x[0] = 'b';
x[1] = 'c';

如果你想列出你所做的所有属性:

for(var i = 0; i < x.length; i++)
console.log(x[i]);// numeric index based access.

性能技巧和陷阱

1.内部缓存length属性

标准数组迭代:

for (var i = 0; i < arr.length; i++) {
    // do stuff
};

鲜为人知的事实:在上述场景中,arr.length在 for 循环的每一步都会读取该属性。就像你在那里调用的任何函数一样:

for (var i = 0; i < getStopIndex(); i++) {
     // do stuff
};

这会无缘无故地降低性能。内部缓存来拯救:

for (var i = 0, len = arr.length; i < len; i++) {
     // enter code here
};

以上就是证明。

2.不要在构造函数中指定数组长度。

// doing this:
var a = new Array(100);
// is very pointless in JS. It will result in an array with 100 undefined values.

// not even this:
var a = new Array();
// is the best way.

var a = [];
// using the array literal is the fastest and easiest way to do things.

数组定义的测试用例可在此处获得。

3.避免使用Array.prototype.push(arr.push)

如果您正在处理大型集合,则直接分配比使用该Array.prototype.push();方法更快。

myArray[i] = 0;myArray.push(0);根据 jsPerf.com 的测试用例,比它更快。

4. 使用数组进行关联赋值是错误的。

它起作用的唯一原因是因为Array扩展Object了 JS 语言核心内部的类。例如,您也可以使用Date();orRegEx();对象。这不会有什么不同。 x['property'] = someValue 必须始终与Objects一起使用。

数组应该只有数字索引。看这个,Google JS 开发指南!避免for (x in arr)循环或arr['key'] = 5;.

这可以很容易地备份,看看这里的例子。

var x = [];
console.log(x.prototype.toString.call);

 将输出:[object Array]

这揭示了核心语言的“类”继承模式。  

var x = new String();
console.log(x.prototype.toString.call);

将输出[object String].

5. 从数组中获取最小值和最大值。

一个鲜为人知但非常强大的技巧:

function arrayMax(arr) {
    return Math.max.apply(Math, arr);
};

, 分别:

function arrayMin(arr) {
    return Math.min.apply(Math, arr);
};

对象

使用一个对象,您只能执行以下操作:

var y = {}或者var y = new Object();

y['first'] = 'firstValue'与 相同y.first = 'firstValue',但您不能对数组执行此操作。对象是为与String键关联访问而设计的。

迭代是这样的:

for (var property in y) {
    if (y.hasOwnProperty(property)) {
        console.log(y.property);
    };
};

性能技巧和陷阱

1. 检查对象是否具有属性。

大多数人使用Object.prototype.hasOwnProperty. 不幸的是,这通常会导致错误的结果导致意外的错误。

这是一个很好的方法:

function containsKey(obj, key) {
    return typeof obj[key] !== 'undefined';
};

2.替换switch语句。

简单但有效的 JS 技巧之一是switch替换。

switch (someVar) {
    case 'a':
        doSomething();
        break;
    case 'b':
        doSomethingElse();
        break;
    default:
        doMagic();
        break;
};

在大多数 JS 引擎中,上述内容非常缓慢。当您查看三种可能的结果时,这并没有什么不同,但是如果您有数十个或数百个结果呢?

上面可以很容易地用一个对象替换。不要添加尾随(),这不是执行函数,而只是存储对它们的引用:

var cases = {
    'a': doSomething,
    'b': doSomethingElse,
    'c': doMagic
};

而不是switch

var x = ???;
if (containsKey(cases, x)) {
    cases[x]();
} else {
    console.log("I don't know what to do!");
};

3. 深度克隆变得容易。

function cloneObject(obj) {
   var tmp = {};
   for (var key in obj) {
       tmp[key] = fastDeepClone(obj[key];
   };
   return tmp;
}

function cloneArr(arr) {
   var tmp = [];
   for (var i = 0, len = arr.length; i < len; i++) {
     tmp[i] = fastDeepClone(arr[i]);
   }
   return tmp;
}


function deepClone(obj) {
   return JSON.parse(JSON.stringify(obj));
};

function isArray(obj) {
   return obj instanceof Array;
}

function isObject(obj) {
  var type = typeof obj;
  return type === 'object' && obj !== null || type === 'function';
}

function fastDeepClone(obj) {
  if (isArray(obj)) {
    return cloneArr(obj);
  } else if (isObject(obj)) {
    return cloneObject(obj);
  } else {
    return obj;
  };
};

这里是深层克隆功能的作用。

自动装箱

作为一种动态类型语言,JavaScript 在原生对象类型方面受到限制:

  • 目的
  • 大批
  • 数字
  • 布尔值
  • 日期
  • 正则表达式
  • 错误

Null 不是类型,typeof null是对象。

有什么问题?原始对象和非原始对象之间有很大的区别。

var s = "str";
var s2 = new String("str");

s它们做同样的事情,你可以在和上调用所有字符串方法s2。然而:

type of s == "string"; // raw data type
type of s2  == "object" // auto-boxed to non-primitive wrapper type
s2.prototype.toString.call == "[object String]";

你可能听说 JS 中的一切都是object. 这并不完全正确,尽管这是一个非常容易犯的错误。

实际上有两种类型,原语和对象,当你调用时s.indexOf("c"),JS 引擎会自动转换s为它的非原语包装类型,在这种情况下object String,所有方法都定义在String.prototype.

这被称为auto-boxing。该Object.prototype.valueOf(obj)方法是一种强制转换从原始到非原始的方法。这与像 Java 这样的语言为它自己的许多原语引入的行为相同,特别是对:int- Integer、double- Double、float- Float 等。

你为什么要关心?

简单的:

function isString(obj) {
   return typeof obj === "string";
}
isString(s); // true
isString(s2); // false

因此,如果s2与您一起创建var s2 = new String("test"),即使对于其他可以想象的简单类型检查,也会得到假阴性。更复杂的对象也会带来严重的性能损失。

正如某些人所说的微优化,但结果确实非常显着,即使对于字符串初始化等极其简单的事情也是如此。让我们从性能方面比较以下两者:

var s1 = "this_is_a_test" 

var s2 = new String("this_is_a_test")

您可能会期望全面匹配性能,但令人惊讶的是,后一个语句使用new String的速度比第一个语句慢 92%,如此处所证明

功能

1. 默认参数

||运算符是最简单的默认方式。为什么它有效?因为真值和假值。

在逻辑条件下评估时,undefinednull将自动转换为false.

一个简单的例子(代码在这里):

function test(x) {
   var param = x || 5;
   // do stuff with x
};

2.OO JS

要理解的最重要的事情是 JavaScriptthis对象不是不可变的。它只是一个可以轻松更改的参考。

在 OO JS 中,我们依靠new关键字来保证 JS 类的所有成员的隐式作用域。即便如此,您也可以通过Function.prototype.call和轻松更改范围Function.prototype.apply

另一个非常重要的事情是Object.prototype. 嵌套在对象原型上的非原始值是共享的,而原始值则不是。

带有示例的代码在这里

一个简单的类定义:

function Size(width, height) {
    this.width = width;
    this.height = height;
};

一个简单的大小类,有两个成员,this.widththis.height.

在类定义中,无论this前面有什么,都会为每个 Size 实例创建一个新引用。

向类添加方法以及为什么“闭包”模式和其他“花哨的名称模式”纯属虚构

这可能是发现最恶意的 JavaScript 反模式的地方。

我们可以通过Size两种方式向我们的类添加方法。

Size.prototype.area = function() {
   return this.width * this.height;
};

或者:

function Size2(width, height) {
   this.width = width;
   this.height = height;
   this.area = function() {
      return this.width * this.height;
   }
}

var s = new Size(5, 10);
var s2 = new Size2(5, 10);



var s3 = new Size2(5, 10);
var s4 = new Size(5, 10);
 // Looks identical, but lets use the reference equality operator to test things:
s2.area === s3.area // false
s.area === s4.area // true

area方法Size2是为每个实例创建的。这完全没用而且很慢,慢了很多。准确地说是 89%。看这里

上述陈述对所有已知的“花式名称模式”中的大约 99% 都有效。记住 JS 中最重要的一点,所有这些都不过是虚构的。

可以提出强有力的架构论据,主要围绕数据封装和闭包的使用。

不幸的是,这些东西在 JavaScript 中绝对没有价值,性能损失根本不值得。我们谈论的是 90% 及以上,这绝对是微不足道的。

3. 限制

因为prototype定义在类的所有实例之间共享,所以您将无法在其中放置非原始设置对象。

Size.prototype.settings = {};

为什么?size.settings每个实例都是相同的。那么原始人是怎么回事呢?

Size.prototype.x = 5; // won't be shared, because it's a primitive.
// see auto-boxing above for primitive vs non-primitive
// if you come from the Java world, it's the same as int and Integer.

要点:

一般的 JS 人会这样写 JS:

var x = {
    doStuff: function(x) {
    },
    doMoreStuff: 5,
    someConstant: 10
}

很好(很好=质量差,难以维护代码),只要您了解这是一个Singleton对象,并且这些函数应该只在全局范围内使用而不this在它们内部引用。

但随后它就变成了绝对糟糕的代码:

var x = {
   width: 10,
   height: 5
}
var y = {
   width: 15,
   height: 10
}

你本可以侥幸逃脱:var x = new Size(10, 5); var y = new Size(15, 5);

打字需要更长的时间,您每次都需要输入相同的内容。再一次,它慢了很多。看这里

整体标准差

这几乎可以在任何地方看到:

   function() {
      // some computation
      var x = 10 / 2;
      var y = 5;
      return {
         width: x,
         height: y
      }
    }

再次使用替代方案:

function() {
    var x = 10 / 2;
    var y = 5;
    return new Size(10, 5);
};

重点:在适当的地方使用类!

为什么?示例 1慢了 93%。看这里。这里的例子是微不足道的,但它们说明了一些在 JS、OO 中被忽略的东西。

不雇用那些认为 JS 没有课程的人,并从谈论“面向对象” JS 的招聘人员那里获得工作,这是一条可靠的经验法则。

闭包

很多人更喜欢它们而不是上面的,因为它给了他们一种数据封装的感觉。除了 90% 的性能急剧下降之外,还有一些同样容易被忽视的事情。内存泄漏。

function Thing(someParam) {
   this.someFn = function() {
     return someParam;
   }
}

您刚刚为someParam. 为什么这很糟糕?首先,它迫使您将类方法定义为实例属性,从而导致性能大幅下降。

其次,它会占用内存,因为闭包永远不会被取消引用。在这里寻找证据。当然,你确实得到了一些虚假的数据封装,但是你使用了三倍的内存,性能下降了 90%。

或者您可以添加@private并获取带有下划线函数名称的方法。

其他非常常见的生成闭包的方法:

function bindSomething(param) {
   someDomElement.addEventListener("click", function() {
     if (param) //do something
     else // do something else
   }, false);
}

param现在是关闭!你如何摆脱它?有各种技巧,一些在这里找到。最好的方法,尽管更严格,是避免一起使用匿名函数,但这需要一种方法来指定事件回调的范围。

据我所知,这种机制仅在 Google Closure 中可用。

单例模式

好的,那我该怎么处理单身人士呢?我不想存储随机引用。这是一个从Google Closure 的 base.js中无耻窃取的绝妙想法

/**
 * Adds a {@code getInstance} static method that always return the same instance
 * object.
 * @param {!Function} ctor The constructor for the class to add the static
 *     method to.
 */
function addSingletonGetter(ctor) {
  ctor.getInstance = function() {
    if (ctor.instance_) {
      return ctor.instance_;
    }
    return ctor.instance_ = new ctor;
  };
};

它是 Java 风格的,但它是一个简单而强大的技巧。您现在可以执行以下操作:

project.some.namespace.StateManager = function() {
   this.x_ = 5;
};
project.some.namespace.prototype.getX = function() { return x; }
addSingletonGetter(project.some.namespace.StateManager);

这有什么用?简单的。在所有其他文件中,每次需要引用project.some.namespace.StateManager时,都可以写: project.some.namespace.StateManager.getInstance(). 这比看起来更棒。

您可以利用类定义(继承、有状态成员等)的好处来拥有全局状态,而不会污染全局命名空间。

单实例模式

您现在可能很想这样做:

function Thing() {
   this.someMethod = function() {..}
}
// and then use it like this:
Thing.someMethod();

这是 JavaScript 中的另一个大禁忌。请记住,只有在使用关键字this时才能保证对象是不可变的。new上述代码背后的魔力很有趣。this实际上是全局范围,因此对您来说毫无意义的是向全局对象添加方法。你猜对了,这些东西永远不会被垃圾收集。

没有什么告诉 JavaScript 使用其他东西。Afunction本身没有范围。要非常小心地处理static属性。重现我曾经读过的一句话,JavaScript 全局对象就像一个公共厕所。有时你别无选择,只能去那里,但尽量减少与表面的接触。

要么坚持上述Singleton模式,要么使用嵌套在命名空间下的设置对象。

JavaScript 中的垃圾收集

JavaScript 是一种垃圾收集语言,但 JavaScript GC 的理解通常很差。重点又是速度。这或许太熟悉了。

// This is the top of a JavaScript file.
var a = 5;
var b = 20;
var x = {};//blabla

// more code
function someFn() {..}

那是糟糕的,性能差的代码。原因很简单。JS 将垃圾收集一个变量并释放它持有的堆内存,仅当该变量被解除作用域时,例如在内存中的任何地方都没有对它的引用。

例如:

function test(someArgs) {
   var someMoreStuf = // a very big complex object;
}
test();

三件事:

  • 函数参数被转换为局部定义
  • 内部声明被提升
  • 当函数完成执行时,分配给内部变量的所有堆内存都会被释放。

为什么?因为它们不再属于“当前”范围。它们被创建、使用和销毁。也没有闭包,因此您使用的所有内存都通过垃圾回收释放。

出于这个原因,你永远不应该,你的 JS 文件永远不应该看起来像这样,因为全局范围只会不断污染内存。

var x = 5;
var y = {..}; //etc;

好吧,现在呢?

命名空间

JS 没有命名空间,所以这不完全是 Java 的等价物,但从代码库管理的角度来看,你会得到你想要的。

var myProject = {};
myProject.settings = {};
myProject.controllers = {};
myProject.controlls.MainController = function() {
    // some class definition here
}

美丽的。一个全局变量。合理的项目结构。通过构建阶段,您可以跨文件拆分项目,并获得适当的开发环境。

您可以从这里实现的目标没有限制。

数一数你的图书馆

有幸在无数代码库上工作过,最后也是最重要的论点是要非常注意您的代码依赖性。我见过程序员随便将 jQuery 添加到堆栈中以获得简单的动画效果等等。

依赖关系和包管理是 JavaScript 世界最长时间没有解决的问题,直到创建了像 Bower 这样的工具。浏览器仍然有些慢,即使它们很快,互联网连接也很慢。

例如,在 Google 的世界里,他们编写整个编译器只是为了节省字节数,而这种方法在很多方面都是 Web 编程的正确心态。我非常重视谷歌,因为他们的 JS 库为谷歌地图等应用程序提供支持,这些应用程序不仅极其复杂,而且在任何地方都可以使用。

可以说,鉴于 JavaScript 的受欢迎程度、可访问性以及在某种程度上非常低的质量,整个生态系统都愿意接受,因此 JavaScript 有大量可用的工具。

对于Hacker News的订阅者来说,如果没有一个新的 JS 库,一天是不会过去的,它们当然很有用,但人们不能忽视这样一个事实,即它们中的许多人在没有任何强烈的新颖性或任何强烈概念的情况下重新实现了完全相同的问题。杀手锏和改进。

在所有新玩具有时间证明它们对整个生态系统的新颖性和有用性并强烈区分周日编码乐趣和生产部署之前,抵制混入所有新玩具的冲动是一个强有力的经验法则。

如果你的<head></head>标签比这篇文章长,那你就错了。

测试你的 JavaScript 知识

一些“完美主义者”水平测试:

于 2012-10-19T12:02:49.297 回答
8

对象的集合?使用这种表示法(JavaScript 数组):

var collection = [ {name:"object 1"} , {name:"object 2"} , {name:"object 3"} ];

要将新元素放入您的收藏:

collection.push( {name:"object 4"} );
于 2012-10-19T11:56:51.870 回答
1

在 JavaScript 中,所有对象都是关联数组。在第一种情况下,您创建了一个数组,在第二种情况下,您创建了一个空对象,它也是数组:)。

因此,在 JS 中,您可以像使用数组一样使用任何对象:

var a = {};
a["temp"] = "test";

作为对象:

var a = {};
a.temp = "test";
于 2012-10-19T11:57:11.673 回答
1

我会使用一个对象数组:

collection = [ 
    { "key":"first key", "value":"first value" }, 
    { "key":"second key", "value":"second value" } 
];

ETC

于 2012-10-19T11:59:14.657 回答
1

1) 是一个数组 2) 是一个对象

使用 Array all 和其他语言一样

与对象也。- 你可以得到值 ab == 1 - 但是在 JS 中你也可以用这样的语法 a["b"] == 1 得到值

  • 当键看起来像“某个键”时,这可能很有用,在这种情况下,您不能使用“链接”
  • 如果 key 是变量,这也很有用

你可以这样写

    function some(f){
var Object = {name: "Boo", age: "foo"}, key;
if(f == true){
   key = "name";
}else{
   key = "age";
}
 return Object[key];
}

但我想把它用作收藏,我必须选择哪个?

这取决于您要存储的数据

于 2012-10-19T12:03:36.437 回答