注意:这个答案是准确的,但并没有完全反映使用 ES6 在 JavaScript 中创建类的新方法class Thing {}
句法。这里的所有内容实际上都适用于 ES6 类,但可能需要一些翻译。
我最初回答了错误的问题。这是您实际提出的问题的答案。我会留下我的其他笔记,以防它们对某人有帮助。
通过在构造函数中向对象添加属性this.prop
与在外面这样做不同Object.prototype.prop
.
-
最重要的区别是,当您将属性添加到函数的原型并从中实例化一个新对象时,该属性是通过逐步继承链在新对象中访问的,而不是直接在对象上访问。
var baseobj = {};
function ObjType1() {
this.prop = 2;
}
function ObjType2() {}
ObjType1.prototype = baseobj;
ObjType2.prototype = baseobj; // these now have the *same* prototype object.
ObjType1.prototype.prop = 1;
// identical to `baseobj.prop = 1` -- we're modifying the prototype
var a = new ObjType1(),
b = new ObjType2();
//a.hasOwnProperty('prop') : true
//b.hasOwnProperty('prop') : false -- it has no local property "prop"
//a: { prop = 2 }, b : { prop = 1 } -- b's "prop" comes from the inheritance chain
baseobj.prop = 3;
//b's value changed because we changed the prototype
//a: { prop = 2 }, b : { prop = 3 }
delete a.prop;
//a is now reflecting the prototype's "prop" instead of its own:
//a: { prop = 3 }, b : { prop = 3 }
-
第二个区别是,向原型添加属性会在代码执行时发生一次,但向构造函数内的对象添加属性会在每次创建新对象时发生。这意味着使用原型性能更好并且使用更少的内存,因为在叶/邻近对象上设置相同的属性之前不需要新的存储。
-
另一个区别是内部添加的函数可以访问私有变量和函数(在构造函数中声明的变量和函数var
, const
, or let
),而基于原型或外部添加的函数则不然,只是因为它们的作用域错误:
function Obj(initialx, initialy) {
var x = initialx,
y = initialy;
this.getX = function() {
return x;
}
var twoX = function() { // mostly identical to `function twoX() { ... }`
return x * 2;
}
this.getTwoX = function() {
return twoX();
}
}
Obj.prototype.getY = function() {
return y; // fails, even if you try `this.y`
}
Obj.prototype.twoY = function() {
return y * 2; // fails
}
Obj.prototype.getTwoY = function() {
return twoY(); // fails
}
var obj = new Obj();
// obj.y : fails, you can't access "y", it is internal
// obj.twoX() : fails, you can't access "twoX", it is internal
// obj.getTwoX() : works, it is "public" but has access to the twoX function
有关 JavaScript 对象、函数和继承的一般说明
-
JavaScript 中的所有非字符串和非标量变量都是对象。 (某些基本类型在使用方法时会进行装箱,例如true.toString()
or 1.2.valueOf()
)。它们的行为都有点像哈希/字典,因为它们具有可以分配给它们的无限(?)数量的键/值对。 JavaScript 中当前的原语列表是: string、number、bigint、boolean、undefined、symbol、null。
-
每个对象都有一个“原型”继承链,一直延伸到基础对象。当您访问对象的属性时,如果该属性在对象本身上不存在,则检查该对象的秘密原型,如果不存在,则检查该对象的原型,依此类推。一些浏览器通过属性公开这个原型__proto__
。获取对象原型的更现代的方法是Object.getPrototypeOf(obj)。常规对象没有prototype
属性,因为该属性用于函数,用于存储该对象will be使用该函数作为构造函数创建的任何新对象的原型。
-
JavaScript 函数是对象的一种特殊情况,除了具有对象的键/值对之外,还具有参数和一系列按顺序执行的语句。
-
每次调用函数对象时,它都会与通过关键字从函数内部访问的另一个对象配对this
。通常,this
对象是函数所属的对象。例如,''.replace()
将字符串文字装箱为String
,然后在替换函数中,this指的是那个对象。另一个例子是当一个函数附加到 DOM 元素时(可能是按钮上的 onclick 函数),那么this
指的是 DOM 元素。您可以手动选择配对this
对象动态使用apply
or call
.
-
当使用 JavaScript 函数调用时new
关键字如var obj = new Obj()
,这会导致特殊的事情发生。如果您没有专门返回任何内容,那么而不是obj
现在包含返回值 of the Obj
函数,它包含这个对象在调用时与该函数配对,这将是一个新的空对象,其继承链中的第一个父对象设置为Obj.prototype
。被调用的Obj()
函数在运行时可以修改新对象的属性。然后返回该对象。
-
您不必太担心关键字constructor
,只要这么说就够了obj.constructor
指向 Obj 函数(这样您就可以找到创建它的东西),但您可能不需要在大多数情况下使用它。
回到你的问题。要了解从构造函数内修改对象属性和修改其原型之间的区别,请尝试以下操作:
var baseobj = {prop1: 'x'};
function TSomeObj() {
this.prop2 = 'y';
};
TSomeObj.prototype = baseobj;
var a = new TSomeObj();
//now dump the properties of `a`
a.prop1 = 'z';
baseobj.prop1 = 'w';
baseobj.prop2 = 'q';
//dump properties of `a` again
delete a.prop1;
//dump properties of `a` again
你会看到这个设置a.prop1
实际上是创建邻近对象的新属性,但它不会覆盖基础对象的 prop1。当您删除时prop1
from a
然后你就得到了继承的prop1
我们改变了。另外,即使我们添加了prop2
after a
被创建,a
仍然拥有该属性。这是因为 javascript 使用原型继承而不是经典传承。当你修改原型时TSomeObj
您还可以修改所有先前实例化的对象,因为它们正在主动继承它。
当您用任何编程语言实例化一个类时,新对象将具有其“构造函数”类(我们通常认为与该对象同义)的属性。在大多数编程语言中,您无法更改类或实例化对象的属性或方法,除非停止程序并更改类声明。
不过,Javascript 允许您修改对象的属性and运行时的“类”,并且该类型类的所有实例化对象也会被修改,除非它们有自己的属性来覆盖修改。对象可以生成对象,对象也可以生成对象,因此它以一条链的方式一直运行到基础Object
班级。我将“类”放在引号中是因为 JavaScript 中确实不存在类这样的东西(即使在 ES6 中,它也主要是语法糖),除了new
关键字允许您使用连接的继承链创建新对象,因此我们将它们称为类,即使它们只是使用构造函数调用的结果new
关键词。
其他一些注意事项:函数有一个Function
构造函数,对象有一个Object
构造函数。原型为Function
构造函数是(惊喜,惊喜)Object
.
在没有运行构造函数的情况下从对象继承
在某些情况下,能够在不运行构造函数的情况下创建新的“对象实例”很有用。您可以从类继承,而无需像这样运行类的构造函数(几乎就像手动执行child.__proto__ = parent
):
function inheritFrom(Class) {
function F() {};
F.prototype = Class.prototype;
return new F();
}
现在更好的方法是Object.setPrototypeOf().