JavaScript中的'new'关键字是什么?

Alo*_*kin 1684 javascript new-operator

newJavaScript中的关键字在第一次遇到时会非常混乱,因为人们倾向于认为JavaScript不是面向对象的编程语言.

  • 它是什么?
  • 它解决了什么问题?
  • 什么时候适当,什么时候不适合?

小智 2082

它做了5件事:

  1. 它创建了一个新对象.该对象的类型只是对象.
  2. 它将此新对象的内部,不可访问,[[prototype]](即__ proto__)属性设置为构造函数的外部可访问原型对象(每个函数对象自动具有原型属性).
  3. 它使this变量指向新创建的对象.
  4. 它执行构造函数,只要this提到就使用新创建的对象.
  5. 它返回新创建的对象,除非构造函数返回非null对象引用.在这种情况下,将返回该对象引用.

注意:构造函数是指new关键字后面的函数,如

new ConstructorFunction(arg1, arg2)
Run Code Online (Sandbox Code Playgroud)

完成此操作后,如果请求新对象的未定义属性,脚本将检查该对象的[[prototype]]对象.这就是你如何在JavaScript中获得类似于传统类继承的东西.

关于这一点最困难的部分是第2点.每个对象(包括函数)都有一个名为[[prototype]]的内部属性.它只能在对象创建时设置,可以是new,使用Object.create,也可以是基于文字(函数默认为Function.prototype,数字为Number.prototype等).它只能用Object.getPrototypeOf(someObject)读取.有没有其他的方式来设置或读取此值.

除了[[prototype]]属性之外,函数还有一个名为prototype的属性,您可以访问和修改这些属性,为您创建的对象提供继承的属性和方法.


这是一个例子:

ObjMaker = function() {this.a = 'first';};
// ObjMaker is just a function, there's nothing special about it that makes 
// it a constructor.

ObjMaker.prototype.b = 'second';
// like all functions, ObjMaker has an accessible prototype property that 
// we can alter. I just added a property called 'b' to it. Like 
// all objects, ObjMaker also has an inaccessible [[prototype]] property
// that we can't do anything with

obj1 = new ObjMaker();
// 3 things just happened.
// A new, empty object was created called obj1.  At first obj1 was the same
// as {}. The [[prototype]] property of obj1 was then set to the current
// object value of the ObjMaker.prototype (if ObjMaker.prototype is later
// assigned a new object value, obj1's [[prototype]] will not change, but you
// can alter the properties of ObjMaker.prototype to add to both the
// prototype and [[prototype]]). The ObjMaker function was executed, with
// obj1 in place of this... so obj1.a was set to 'first'.

obj1.a;
// returns 'first'
obj1.b;
// obj1 doesn't have a property called 'b', so JavaScript checks 
// its [[prototype]]. Its [[prototype]] is the same as ObjMaker.prototype
// ObjMaker.prototype has a property called 'b' with value 'second'
// returns 'second'
Run Code Online (Sandbox Code Playgroud)

这就像类继承一样,因为现在,您使用的任何对象new ObjMaker()也似乎都继承了'b'属性.

如果你想要类似子类的东西,那么你这样做:

SubObjMaker = function () {};
SubObjMaker.prototype = new ObjMaker(); // note: this pattern is deprecated!
// Because we used 'new', the [[prototype]] property of SubObjMaker.prototype
// is now set to the object value of ObjMaker.prototype.
// The modern way to do this is with Object.create(), which was added in ECMAScript 5:
// SubObjMaker.prototype = Object.create(ObjMaker.prototype);

SubObjMaker.prototype.c = 'third';  
obj2 = new SubObjMaker();
// [[prototype]] property of obj2 is now set to SubObjMaker.prototype
// Remember that the [[prototype]] property of SubObjMaker.prototype
// is ObjMaker.prototype. So now obj2 has a prototype chain!
// obj2 ---> SubObjMaker.prototype ---> ObjMaker.prototype

obj2.c;
// returns 'third', from SubObjMaker.prototype

obj2.b;
// returns 'second', from ObjMaker.prototype

obj2.a;
// returns 'first', from SubObjMaker.prototype, because SubObjMaker.prototype 
// was created with the ObjMaker function, which assigned a for us
Run Code Online (Sandbox Code Playgroud)

在最终找到这个页面之前,我读了很多关于这个主题的垃圾,这里用漂亮的图表很好地解释了这个问题.

  • 只是想补充一点:实际上有一种方法可以通过\ _\_\_\_原始\ _\_ _来访问内部[[prototype]].然而,这是非标准的,并且仅由相对较新的浏览器(而不是所有浏览器)支持.有一种标准化的方法,即Object.getPrototypeOf(obj),但它是Ecmascript3.1,并且本身仅支持新的浏览器 - 再次.通常建议不要使用该属性,因为内部的内容变得非常复杂. (45认同)
  • @LonelyPixel`new`存在_so你不必编写工厂方法来构造/复制函数/对象.这意味着,"复制这个,使它就像它的父级'类一样;高效,正确地执行;并存储只有我自己可以访问的继承信息,JS,内部".为此,它修改了新对象的其他无法访问的内部`原型`,以不透明方式封装继承的成员,模仿经典的OO继承链(不是运行时可修改的).你可以在没有`new`的情况下模拟这个,但继承将是运行时可修改的.好?坏?由你决定. (11认同)
  • 要添加的小点:对构造函数的调用,当以new关键字开头时,会自动返回创建的对象; 没有必要在构造函数中显式返回它. (10认同)
  • 问题:如果将"ObjMaker"定义为返回值的函数,会出现什么情况? (9认同)
  • 有一条说明"请注意,这种模式已被弃用!".设置类原型的正确最新模式是什么? (7认同)
  • @tomp`SubObjMaker.prototype = Object.create(ObjMaker.prototype);`你可能需要一个[shim](https://github.com/es-shims/es5-shim)来表示`Object.create`. (6认同)
  • 这一切都很好,很有趣,但它没有回答问题的重要部分.特别是,当使用`new`是合适的而不是.我可以编写一个函数来返回一个带有函数的对象,就像你可以编写一个"类"定义一样.当我使用`var obj = new func()`以及`var obj = func()`时,我可以使用这种对象的方法,例如`obj.method()`在两种情况下都能正常工作.我现在也一样.我不使用`this`关键字,但我不需要它.我需要`new`吗? (4认同)
  • 注意`ObjMaker = function(){this.a ='first';};`当你只调用ObjMaker()时,你将`first`分配给全局对象,通常是窗口,但是当你调用新的ObjMaker()时,这是指向当前对象,因此您可以调用`new ObjMaker().a` (3认同)
  • 我认为这也在经典[MDC docs](https://developer.mozilla.org/en/JavaScript/Guide/Details_of_the_Object_Model)中进行了解释. (2认同)
  • 您实际上可以通过__ proto __或this.constructor.prototype访问对象的内部原型对象. (2认同)
  • George Mauer是对的,只是我认为最好考虑"从基础对象创建一个新对象"和"将基础对象的原型复制到新对象",而不是创建一个"新实例",这是一个概念这真的属于基于类的继承,而不是基于原型的继承.我希望编辑这个问题以反映这一点...... (2认同)
  • @tomp我找到了一个很好的[博客](http://blog.brillskills.com/2013/09/javascript-subclassing-using-object-create/)更多地解释了这个问题. (2认同)

Jul*_*anR 387

假设你有这个功能:

var Foo = function(){
  this.A = 1;
  this.B = 2;
};
Run Code Online (Sandbox Code Playgroud)

如果你将它称为独立函数,如下所示:

Foo();
Run Code Online (Sandbox Code Playgroud)

执行此函数将向window对象添加两个属性(AB).它将它添加到window因为window是在执行它时调用函数的对象,并且this在函数中是调用函数的对象.至少在Javascript中.

现在,用这样的方式调用它new:

var bar = new Foo();
Run Code Online (Sandbox Code Playgroud)

添加new到函数调用时会发生的情况是创建一个新对象(仅var bar = new Object()),并且this函数内部指向Object刚刚创建的新对象,而不是指向调用该函数的对象.所以bar现在是一个具有属性A和对象的对象B.任何函数都可以是构造函数,它并不总是有意义的.

  • 取决于执行上下文.就我而言(Qt脚本),它只是一个全局对象. (6认同)
  • 这会导致更多的内存使用吗? (2认同)
  • *因为window是调用函数的对象* - 必须是:因为window是**包含**函数的对象. (2认同)
  • @Taurus 在 Web 浏览器中,非方法函数将是隐式的 `window` 方法。即使在关闭,即使匿名。然而,在这个例子中,它是一个简单的窗口方法调用:`Foo();` => `[default context].Foo();` => `window.Foo();`。在这个表达式中,`window` 是 *context*(不仅仅是 *caller*,这无关紧要)。 (2认同)
  • @金牛座 基本上是的。然而,在 ECMA 6 和 7 中,事情更加复杂(参见 lambda、类等)。 (2认同)

bas*_*kum 160

除了丹尼尔霍华德的回答,这里是new(或至少似乎做):

function New(func) {
    var res = {};
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return res;
}
Run Code Online (Sandbox Code Playgroud)

var obj = New(A, 1, 2);
Run Code Online (Sandbox Code Playgroud)

相当于

var obj = new A(1, 2);
Run Code Online (Sandbox Code Playgroud)

  • 我发现javascript比英文更容易理解:v (68认同)
  • @tomp你可以覆盖原型属性,只需编写`A.prototype = null;`在这种情况下`new A()`将导致对象,那就是内部原型指向`Object`对象:http:// jsfiddle.net/Mk42Z/ (6认同)
  • @Oriol 感谢您的评论。您所说的是真的,任何实际测试都应该以更可靠的方式进行。然而,我认为对于这个概念性的答案,`typeof` 测试只是让我们更容易理解幕后发生的事情。 (3认同)
  • 类型检查可能是错误的,因为主机对象可能产生与"对象"或"功能"不同的东西.为了测试某些东西是否是一个对象,我更喜欢`Object(ret)=== ret`. (2认同)

Anu*_*l S 105

让初学者更好地理解它

在浏览器控制台中尝试以下代码.

function Foo() { 
    return this; 
}

var a = Foo();       //returns window object
var b = new Foo();   //returns empty object of foo

a instanceof Window;  // true
a instanceof Foo;     // false

b instanceof Window;  // false
b instanceof Foo;     // true
Run Code Online (Sandbox Code Playgroud)

现在你可以阅读社区wiki的答案 :)

  • 很高兴指出为什么会这样. (5认同)
  • 好答案.另外 - 省略`return this;`产生相同的输出. (4认同)

med*_*iev 37

所以它可能不是用于创建对象的实例

它完全用于此.你定义一个函数构造函数,如下所示:

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

var john = new Person('John');
Run Code Online (Sandbox Code Playgroud)

然而,ECMAScript的额外好处是你可以扩展.prototype属性,所以我们可以做类似......

Person.prototype.getName = function() { return this.name; }
Run Code Online (Sandbox Code Playgroud)

从这个构造函数创建的所有对象现在都有一个getName因为它们可以访问的原型链.

  • 按惯例,它应该是大写的人. (23认同)
  • 顺便说一下,这就是你使用.className而不是.class来设置CSS类的原因 (11认同)
  • 函数构造函数就像类一样使用,没有`class`关键字,但你几乎可以做同样的事情. (6认同)
  • kindof 是一个 class 关键字 - class 被保留以供将来使用 (2认同)

Mic*_*ael 27

JavaScript 一种面向对象的编程语言,它完全用于创建实例.它是基于原型的,而不是基于类的,但这并不意味着它不是面向对象的.

  • 我想说JavaScript似乎比所有那些基于类的语言更加面向对象.在JavaScript中,您编写的所有内容都会立即成为一个对象,但在基于类的语言中,您首先要编写声明,然后才能创建类的特定实例(对象).JavaScript原型似乎模糊地提醒所有基于类的语言的VTABLE东西. (6认同)

Wil*_*een 20

概括:

new关键字在 javascript 中用于从构造函数创建对象。该new关键字的构造函数调用之前放置,并会做以下几件事:

  1. 创建一个新对象
  2. 将此对象的原型设置为构造函数的原型属性
  3. this关键字绑定到新创建的对象并执行构造函数
  4. 返回新创建的对象

例子:

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

const doggie = new Dog(12);

console.log(doggie);
console.log(Object.getPrototypeOf(doggie) === Dog.prototype) // true
Run Code Online (Sandbox Code Playgroud)

究竟发生了什么:

  1. const doggie 说:我们需要内存来声明一个变量。
  2. 赋值运算符=说:我们将用后面的表达式初始化这个变量=
  3. 表达式是new Dog(12)。JS 引擎看到 new 关键字,创建一个新对象并将原型设置为 Dog.prototype
  4. 构造函数以this设置为新对象的值执行。在此步骤中,将年龄分配给新创建的小狗对象。
  5. 返回新创建的对象并将其分配给变量 doggie。


Gre*_*reg 14

Javascript是一种动态编程语言,支持面向对象的编程范例,它用于创建对象的新实例.

对象不需要类 - Javascript是基于原型的语言.


RBT*_*RBT 9

已经有一些非常好的答案,但是我要发布一个新的答案,以强调我对以下情况III的观察,即在要执行的函数中有显式return语句时会发生什么new。看一下以下情况:

情况一

var Foo = function(){
  this.A = 1; 
  this.B = 2;
};
console.log(Foo()); //prints undefined
console.log(window.A); //prints 1
Run Code Online (Sandbox Code Playgroud)

上面是调用指向的匿名函数的简单案例Foo。当您调用此函数时,它将返回undefined。由于没有显式的return语句,因此JavaScript解释器会return undefined;在函数末尾强制插入一条语句。此处的窗口是this获取新对象AB属性的调用对象(上下文)。

情况二

var Foo = function(){
  this.A = 1;
  this.B = 2;
};
var bar = new Foo();
console.log(bar()); //illegal isn't pointing to a function but an object
console.log(bar.A); //prints 1
Run Code Online (Sandbox Code Playgroud)

JavaScript解释器在此看到new关键字创建了一个新对象,该对象充当所this指向的匿名函数的调用对象(上下文)Foo。在这种情况下AB成为新创建的对象(代替窗口对象)的属性。由于您没有任何显式的return语句,因此JavaScript解释程序会强制插入return语句以返回由于使用new关键字而创建的新对象。

情况三

var Foo = function(){
  this.A = 1;
  this.B = 2;
  return {C:20,D:30}; 
};
var bar = new Foo();
console.log(bar.C);//prints 20
console.log(bar.A); //prints undefined. bar is not pointing to the object which got created due to new keyword.
Run Code Online (Sandbox Code Playgroud)

在这里,JavaScript解释器再次看到new关键字创建了一个新对象,该对象充当所this指向的匿名函数的调用对象(上下文)Foo。再次,AB成为新创建对象的属性。但是这次您有一个显式的return语句,因此JavaScript解释器将不会做任何事情。

在情况III中要注意的是,由于new关键字而创建的对象已从雷达中丢失。bar实际上指向了一个完全不同的对象,该对象不是JavaScript解释程序由于new关键字而创建的对象。

引用Java Scripit的David Flanagan:《权威指南》(第6版),第二章。4,第62页:

计算对象创建表达式时,JavaScript首先创建一个新的空对象,就像对象初始化程序{}创建的对象一样。接下来,它将使用指定的参数调用指定的函数,并将新对象作为this关键字的值传递。然后,函数可以使用它来初始化新创建的对象的属性。编写用作构造函数的函数不会返回值,并且对象创建表达式的值是新创建和初始化的对象。如果构造函数确实返回了对象值,则该值将成为对象创建表达式的值,并且新创建的对象将被丢弃。

- 其他信息 -

上述情况的代码段中使用的函数在JS world中具有特殊名称,如下所示:

情况一和二 -构造函数

情况三 -工厂功能。工厂函数不应new我用来解释当前线程中概念的关键字一起使用。

您可以在线程中了解它们之间的区别。

  • 你的情况3,是一个gr8观察 (2认同)

小智 5

有时候代码比文字容易:

var func1 = function (x) { this.x = x; }                    // used with 'new' only
var func2 = function (x) { var z={}; z.x = x; return z; }   // used both ways
func1.prototype.y = 11;
func2.prototype.y = 12;

A1 = new func1(1);      // has A1.x  AND  A1.y
A2 =     func1(1);      // undefined ('this' refers to 'window')
B1 = new func2(2);      // has B1.x  ONLY
B2 =     func2(2);      // has B2.x  ONLY
Run Code Online (Sandbox Code Playgroud)

对我来说,只要我没有原型,我就会使用func2的样式,因为它使我在函数内部和外部都有更多的灵活性。

  • `B1 = new func2(2);`<-为什么它不会包含`B1.y`? (3认同)

Juz*_*Ali 5

new关键字改变正在运行在其下的功能的上下文和指针返回到该上下文。

当您不使用new关键字时,函数在其下Vehicle()运行的上下文与您从中调用该Vehicle函数的上下文相同。该this关键字将指向同一个语境。当您使用时new Vehicle(),会创建一个新上下文,因此this函数内的关键字引用了新上下文。您得到的是新创建的上下文。


Yil*_*maz 5

 " Every object (including functions) has this internal property called [[prototype]]" 
Run Code Online (Sandbox Code Playgroud)

每个函数都有一个原型对象,它被自动设置为用该函数创建的对象的原型。

你们可以很容易地检查:

const a = { name: "something" };
console.log(a.prototype); // undefined because it is not directly accessible

const b = function () {
  console.log("somethign");};

console.log(b.prototype); // returns b {}
Run Code Online (Sandbox Code Playgroud)

但是每个函数和对象都有__proto__指向该对象或函数原型的属性。 __proto__并且prototype是 2 个不同的术语。我想我们可以发表这样的评论:“每个对象都通过proto链接到原型”但__proto__在 javascript 中不存在。浏览器添加此属性只是为了帮助调试。

console.log(a.__proto__); // returns {}
console.log(b.__proto__); // returns [Function]
Run Code Online (Sandbox Code Playgroud)

你们可以很容易地在终端上检查这个。那么什么是构造函数。

function CreateObject(name,age){
    this.name=name;
    this.age =age
}
Run Code Online (Sandbox Code Playgroud)

首先要注意的5件事:

1- 当用 调用构造函数时new,函数的内部 [[Construct]] 方法被调用来创建一个新的实例对象并分配内存。

2- 我们没有使用return关键字。new会处理的。

3- 函数名称大写,因此当开发人员看到您的代码时,他们可以理解他们必须使用new关键字。

4- 我们不使用箭头函数。因为this参数的值是在创建箭头函数的那一刻获取的,即“窗口”。箭头函数是词法范围的,而不是动态的。这里的词法是指本地。箭头函数携带其本地“this”值。

5- 与常规函数不同,箭头函数永远不能用 new 关键字调用,因为它们没有 [[Construct]] 方法。箭头函数也不存在原型属性。

const me=new CreateObject("yilmaz","21")
Run Code Online (Sandbox Code Playgroud)

new 调用该函数,然后创建一个空对象 {},然后添加值为“name”的“name”键和带有参数“age”值的“age”键。

当我们调用一个函数时,会使用“this”和“arguments”创建一个新的执行上下文,这就是“new”可以访问这些参数的原因。

默认情况下,构造函数中的 this 将指向“window”对象,但会new更改它。“this”指向创建的空对象 {},然后将属性添加到新创建的对象。如果您有任何未定义“this”属性的变量,则不会将其添加到对象中。

function CreateObject(name,age){
    this.name=name;
    this.age =age;
    const myJob="developer"
}
Run Code Online (Sandbox Code Playgroud)

myJob 属性不会添加到对象中,因为没有任何内容引用新创建的对象。

   const me= {name:"yilmaz",age:21} // there is no myJob key
Run Code Online (Sandbox Code Playgroud)

一开始我说每个函数都有“原型”属性,包括构造函数。我们可以向构造函数的原型添加方法,因此从该函数创建的每个对象都可以访问它。

 CreateObject.prototype.myActions=function(){ //define something}
Run Code Online (Sandbox Code Playgroud)

现在“me”对象可以使用“myActions”方法。

javascript 有内置的构造函数:Function,Boolean,Number,String..

如果我创建

const a = new Number(5);
console.log(a);  // [Number: 5]
console.log(typeof a); // object
Run Code Online (Sandbox Code Playgroud)

通过 using 创建的任何东西new都具有对象类型。现在“a”可以访问存储在Number.prototype 中的所有方法。如果我定义

const b = 5;
console.log(a === b);//false
Run Code Online (Sandbox Code Playgroud)

a 和 b 是 5 但 a 是对象而 b 是原始的。即使 b 是原始类型,当它被创建时,javascript 会自动用 Number() 包装它,所以 b 可以访问 Number.prototype 中的所有方法。

当您想创建多个具有相同属性和方法的相似对象时,构造函数很有用。这样您就不会分配额外的内存,因此您的代码将更有效地运行。