JavaScript中的new
关键字在初次遇到时可能会让人感到困惑,因为人们往往认为JavaScript不是面向对象编程语言。
- 它是什么?
- 它解决了哪些问题?
- 什么情况下使用它是适当的,什么情况下不是?
JavaScript中的new
关键字在初次遇到时可能会让人感到困惑,因为人们往往认为JavaScript不是面向对象编程语言。
它有以下五个作用:
[[prototype]]
属性(即 __proto__
)设置为构造函数的外部、可访问的 prototype
对象(每个函数对象都自动具有 prototype
属性)。this
变量指向新创建的对象。this
时都会使用该对象。null
的对象引用。在这种情况下,返回该对象引用。注意:此处的 构造函数 指的是在 new
关键字后面的函数,如下所示:
new ConstructorFunction(arg1, arg2)
[[prototype]]
对象是否有该属性。这就是如何在JavaScript中获得类似传统类继承的方法。[[prototype]]
的内部属性。它只能在对象创建时使用new
、Object.create
或基于文字设置(函数默认为Function.prototype
,数字默认为Number.prototype
等)。只能使用Object.getPrototypeOf(someObject)
来读取此值。没有其他获取或设置此值的方法。[[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 just `{}`. 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'
这有点像类继承,因为现在使用 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
在找到这个页面之前,我读了很多关于这个主题的垃圾文章。在这个页面上,用漂亮的图解很好地解释了这个问题。
ObjMaker
定义为返回值的函数,会有什么不同? - Jim Blacklernew
的存在是为了让你无需编写工厂方法来构造/复制函数/对象。它的意思是,“复制这个,使其与其父类完全一样;高效且正确地执行此操作,并存储继承信息,该信息仅对我(JS)内部可访问”。为此,它修改了新对象的原本不可访问的内部prototype
,将继承成员不透明地封装起来,模拟经典的OO继承链(这些链是不能在运行时修改的)。你可以在没有new
的情况下模拟这种效果,但继承会在运行时可修改。好还是坏?由你决定。 - Engineer假设你有以下函数:
var Foo = function(){
this.A = 1;
this.B = 2;
};
如果您像这样单独调用此函数:
Foo();
执行这个函数将向window
对象添加两个属性(A
和B
)。它添加到window
对象中,因为当像这样执行函数时,window
是调用函数的对象,在JavaScript中,函数中的this
指向调用该函数的对象。
现在,通过使用new
调用它:
var bar = new Foo();
当你在函数调用中添加new
时,会创建一个新的对象(就像var bar = new Object()
),并且函数内的this
指向刚刚创建的新Object
,而不是调用该函数的对象。因此,bar
现在是具有属性A
和B
的对象。任何函数都可以是构造函数;只是有时可能没有意义。window
的方法,即使在闭包中,甚至是匿名函数。然而,在示例中,它只是对窗口的简单方法调用:Foo();
=> [默认上下文] .Foo();
=> window.Foo();
。在这个表达式中,window
是上下文(不仅是调用者,这并不重要)。 - Dávid Horváth除了Daniel Howard的答案,这里是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;
}
虽然
var obj = New(A, 1, 2);
等同于
var obj = new A(1, 2);
func.prototype
怎么可能是 null
?您能详细解释一下吗? - Tom PažourekA.prototype = null;
来覆盖原型属性。在这种情况下,new A()
将得到一个对象,其内部原型指向Object
对象:http://jsfiddle.net/Mk42Z/ - basilikumObject(ret) === ret
。 - Orioltypeof
测试只是使我们更容易理解在幕后发生了什么。 - basilikum在浏览器控制台中尝试以下代码。
function Foo() {
return this;
}
var a = Foo(); // Returns the 'window' object
var b = new Foo(); // Returns an empty object of foo
a instanceof Window; // True
a instanceof Foo; // False
b instanceof Window; // False
b instanceof Foo; // True
现在您可以阅读社区 Wiki 回答 :)
return this;
也会产生相同的输出结果。 - Nelureturn this;
不改变行为的解释是,运算符new
在创建新对象和执行构造函数方面是神奇的,如果构造函数的返回值是undefined
(没有返回语句或只有return;
),或者是null
(特殊情况:return null;
),则新创建的对象(构造函数内部的this
)将被用作new
运算符的值,否则new
的值是返回的值。我不知道这种行为的原理,但我猜测是“由于历史原因”。 - Mikko Rantalainen所以它可能不是用来创建对象实例的。
它就是用来创建对象实例的。你可以像这样定义一个函数构造器:
function Person(name) {
this.name = name;
}
var john = new Person('John');
然而 ECMAScript 的额外好处在于您可以使用 .prototype
属性进行扩展,因此我们可以做出这样的事情...
Person.prototype.getName = function() { return this.name; }
由于它们可以访问原型链,从该构造函数创建的所有对象现在都将具有getName
。
new
关键字用于 JavaScript 中从构造函数创建对象。在调用构造函数之前,必须将 new
关键字放置在其前面,并执行以下操作:
this
关键字绑定到新创建的对象并执行构造函数function Dog (age) {
this.age = age;
}
const doggie = new Dog(12);
console.log(doggie);
console.log(Object.getPrototypeOf(doggie) === Dog.prototype) // true
具体发生了什么:
const doggie
表示:我们需要内存来声明一个变量。=
表示:我们将使用=
后面的表达式来初始化这个变量。new Dog(12)
。JavaScript引擎看到new关键字,创建一个新对象并将原型设置为Dog.prototype。this
值设置为新对象来执行。在这一步中,年龄被分配给新创建的小狗对象。var Foo = function(){
this.A = 1;
this.B = 2;
};
console.log(Foo()); //prints undefined
console.log(window.A); //prints 1
return undefined;
语句。因此,上面的代码示例等同于:var Foo = function(){
this.A = 1;
this.B = 2;
return undefined;
};
console.log(Foo()); //prints undefined
console.log(window.A); //prints 1
Foo
函数时,window
是默认的调用对象(上下文的this
),会获得新的A
和B
属性。
案例二:
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
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.
案例 # | 名称 |
---|---|
案例 I | 构造函数 |
案例 II | 构造函数 |
案例 III | 工厂函数 |
您可以在此讨论串中了解构造函数和工厂函数的区别。
案例 III 中的代码异味 - 不应该使用 new 关键字来调用工厂函数,但我在上面的代码片段中故意这样做只是为了解释概念。
new
关键字会改变函数运行时的上下文,并返回一个指向该上下文的指针。new
关键字时,Vehicle()
函数运行的上下文与调用Vehicle
函数的上下文相同。关键字this
将引用同一上下文。当使用new Vehicle()
时,将创建一个新的上下文,因此函数内部的关键字this
将引用新的上下文。你会得到新创建的上下文。