你什么时候会在javascript中使用.contat()

When would you use .concat() in javascript

本文关键字:contat 什么时候 javascript      更新时间:2023-09-26

为什么/何时会使用.concat()代替赋值运算符?

即,如果我试图组合以下内容:

var p1 = "My name is ";
var p2 = "Joe";
var sen = p1+p2;
//Or you could use concat to do the same
var sen2 = p1.concat(p2);
//My question is, why would you ever use the latter?

有时最好查阅文档:Array.concat和String.concat.

简单地说,Array.concat()用于创建一个新数组,相当于所有传入对象(数组或其他)的平面合并。String.concat()用于创建一个新字符串,相当于合并所有传入的字符串。

然而,正如MDN所暗示的,不应该使用String.concat(),因为分配+, +=运算符要快得多。那你为什么要用String.concat()呢?你不会的。那为什么要呢?这是规范的一部分:见第111-112页(第15.5.4.6节)。

那么关于为什么String.Concat如此缓慢的问题呢。我对Chrome的V8引擎进行了一些挖掘。首先,在幕后,这就是对String.prototype.concat的调用:

// ECMA-262, section 15.5.4.6
// https://github.com/v8/v8/blob/master/src/string.js#L64
function StringConcat(other /* and more */) {  // length == 1
  CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
  var len = %_ArgumentsLength();
  var this_as_string = TO_STRING_INLINE(this);
  if (len === 1) {
    return this_as_string + other;
  }
  var parts = new InternalArray(len + 1);
  parts[0] = this_as_string;
  for (var i = 0; i < len; i++) {
    var part = %_Arguments(i);
    parts[i + 1] = TO_STRING_INLINE(part);
  }
  return %StringBuilderConcat(parts, len + 1, "");
}

正如您所看到的,所有实际工作都发生在StringBuilderConcat中,然后它调用一个StringBuilderConcatHelper,最后它调用String::WriteToFlat来构建一个字符串。这些都是非常长的函数,为了简洁起见,我已经删掉了大部分。但如果你想寻找你的自我,可以看看github:

StringBuilderConcat

// https://github.com/v8/v8/blob/master/src/runtime.cc#L7163
RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
  // ...
  StringBuilderConcatHelper(*special,
                              answer->GetChars(),
                              FixedArray::cast(array->elements()),
                              array_length);
  // ...
}

StringBuilderConcatHelper

template <typename sinkchar>
static inline void StringBuilderConcatHelper(String* special,
                                             sinkchar* sink,
                                             FixedArray* fixed_array,
                                             int array_length) {
  // ...
  String::WriteToFlat(string, sink + position, 0, element_length);
  // ...
}

String::WriteToFlat

// https://github.com/v8/v8/blob/master/src/objects.cc#L8373
template <typename sinkchar>
void String::WriteToFlat(String* src,
                         sinkchar* sink,
                         int f,
                         int t) {
  String* source = src;
  int from = f;
  int to = t;
  while (true) {
      // ...
      // Do a whole bunch of work to flatten the string
      // ...
    }
  }
}

现在,分配途径有什么不同?让我们从JavaScript添加功能开始:

// ECMA-262, section 11.6.1, page 50.
// https://github.com/v8/v8/blob/master/src/runtime.js#L146
function ADD(x) {
  // Fast case: Check for number operands and do the addition.
  if (IS_NUMBER(this) && IS_NUMBER(x)) return %NumberAdd(this, x);
  if (IS_STRING(this) && IS_STRING(x)) return %_StringAdd(this, x);
  // Default implementation.
  var a = %ToPrimitive(this, NO_HINT);
  var b = %ToPrimitive(x, NO_HINT);
  if (IS_STRING(a)) {
    return %_StringAdd(a, %ToString(b));
  } else if (IS_STRING(b)) {
    return %_StringAdd(%NonStringToString(a), b);
  } else {
    return %NumberAdd(%ToNumber(a), %ToNumber(b));
  }
}

首先要注意的是,没有循环,与上面的StringConcat相比,它要短得多。但我们感兴趣的大部分工作都发生在%_StringAdd函数中:

// https://github.com/v8/v8/blob/master/src/runtime.cc#L7056
RUNTIME_FUNCTION(Runtime_StringAdd) {
  HandleScope scope(isolate);
  DCHECK(args.length() == 2);
  CONVERT_ARG_HANDLE_CHECKED(String, str1, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, str2, 1);
  isolate->counters()->string_add_runtime()->Increment();
  Handle<String> result;
  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
      isolate, result, isolate->factory()->NewConsString(str1, str2));
  return *result;
}

实际上,这很简单,一些计数器和对具有左右操作数的NewConsString的调用。NewConsString也很简单:

// https://github.com/v8/v8/blob/master/src/ast-value-factory.cc#L260
const AstConsString* AstValueFactory::NewConsString(
    const AstString* left, const AstString* right) {
  // This Vector will be valid as long as the Collector is alive (meaning that
  // the AstRawString will not be moved).
  AstConsString* new_string = new (zone_) AstConsString(left, right);
  strings_.Add(new_string);
  if (isolate_) {
    new_string->Internalize(isolate_);
  }
  return new_string;
}

所以这只是返回一个新的AstConsString,那是什么:

// https://github.com/v8/v8/blob/master/src/ast-value-factory.h#L117
class AstConsString : public AstString {
 public:
  AstConsString(const AstString* left, const AstString* right)
      : left_(left),
        right_(right) {}
  virtual int length() const OVERRIDE {
    return left_->length() + right_->length();
  }
  virtual void Internalize(Isolate* isolate) OVERRIDE;
 private:
  friend class AstValueFactory;
  const AstString* left_;
  const AstString* right_;
};

这个看起来一点也不像绳子。它实际上是一个"抽象语法树",这个结构形成了一个"绳索",可以有效地修改字符串。事实证明,现在大多数其他浏览器在添加字符串时都使用这种类型或rope结构。

由此得出的结论是,加法途径使用了更有效的数据结构,其中StringConcat在不同的数据结构下做的工作要多得多。

根据Douglas Crockford的Javascript:The Good Parts:

concat方法通过连接其他字符串来生成新字符串在一起很少使用,因为+运算符更方便

Concat不仅不太方便,而且速度较慢:Benchmark

在MDN的文档页面上:

强烈建议使用赋值运算符(+,+=)而不是concat方法。

Javascript有一些不太理想的部分。每种语言都至少有一些不好的部分。不要认为你必须使用任何语言的每一部分。