let myObj = {
key1: 'value1'
key2: 'value2'
}
let myObj = new Object();
myObj.key = value
两种不同的创建模式有什么区别呢?
1.创建一个空对象,构造函数中的this会指向这个对象
2.这个新对象会被链接到原型
3.执行构造函数方法,其属性和方法都会被添加到this引用的对象中
4.如果构造函数中没有返回新对象,那么返回this,即创建新对象;否则,返回构造函数中返回的对象。
new和字面量创建对象的区别: 1.字面量创建对象,不会调用Object构造函数,简洁且性能更好;
2.new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。
string、boolean、number、null、undefined、object
。string、boolean、number、null、undefined
)本身并不是对象。String、Number、Boolean、Object、Function、Array、Data、RegExp、Error
。
在 JavaScript 中,它们实际上是一些内置函数。这些内置函数可以当作构造函数 (由 new 产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。
举例来说: let strPrimitive = "I am a string"; typeof strPrimitive; // "string" strPrimitive instanceof String; // false var strObject = new String( "I am a string" ); typeof strObject; // "object" strObject instanceof String; // true // 检查 sub-type 对象 Object.prototype.toString.call( strObject ); // [object String]
原始值 "I am a string" 并不是一个对象,它只是一个字面量,并且是一个不可变的值。 如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为 String 对象。 幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。 例如:
let strPrimitive = "I am a string";
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"
使用以上两种方法,我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这 样做,是因为引擎自动把字面量转换成 String 对象,所以可以访问属性和方法。
1.对于字符串字面量(string)、数值字面量(number)、布尔字面量(boolean)
来说,我们都可以直接在其上面访问属性或者方法,之所以可以这样做,是因为引擎自动把字面量转换成 String、Number、Boolean对象
,所以可以访问属性和方法。
2.null、undefined
没有对应的构造形式,它们只有文字形式。相反,Date
只有构造(new Date(..)),没有文字形式。
3.对于 Object、Array、Function、RegExp
来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。
4.Error
对象很少在代码中显式创建,一般是在抛出异常时被自动创建。也可以使用 new Error(..) 这种构造形式来创建,不过一般来说用不着。
ES6 增加了可计算属性名,也可以叫做可拼接,因为字符串中的“+“、“*”运算符会被js引擎解析为拼接 可以在文字形式中使用 [] 包裹一个表达式来当作属性名:
let prefix = "foo";
let myObject = {
[prefix + "bar"]:"hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
可计算属性名最常用的场景可能是 ES6 的符号(Symbol)。简单来说,Symbol是一种新的基础数据类型,包含一个不透明且无法预测的值(从技术角度来说就是一个字符串)。一般来说你不会用到符号的实际值(因为理论上来说在不 同的 JavaScript 引擎中值是不同的),所以通常你接触到的是符号的名称,比如 Symbol. Something
let myObject = {
[Symbol.Something]: "hello world"
}
slice
、concat
、Array.from()
Object.assign()
、JSON.parse(JSON.stringify(obj))
不过使用JSON.parse(JSON.stringify(obj))
的话,undefined
、任意的函数
、symbol
在序列化过程中会被忽略(出现在非数组对象的属性中时)或者被转换成null
(出现在数组中时)在这里扩展一下关于浅拷贝与深拷贝:
浅拷贝:只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。仅仅会针对第一层的数据进行处理,深层嵌套的数据不会进行处理。
深拷贝:创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。
浅拷贝的两种方法:
1.使用for in 遍历对象 挨个赋值
2.使用Object.assign()方法
let objA = {
id: 1,
name: 'andy',
msg: {
age: 18
}
}
let objB = {}
for(var k in objA) {
// k 属性名 obj[k]属性值
objB[k] = objA[k]
}
Object.assign(objB,objA)
objB.msg.age = 20
console.log(objB);
console.log(objA);
Object.assign的源码:
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) { //arguments为全部的对象集合
var source = arguments[i]; //单个对象
//遍历一个对象的自身和继承来的属性,
//常常配合hasOwnProperty筛选出对象自身的属性
for (var key in source) {
//使用call方法,避免原型对象扩展带来的干扰
if (Object.prototype.hasOwnProperty.call(source, key)) { ///判断是否为source自身的属性。 (非继承)说明Object.assign只能加源对象的自身的属性,不算继承的。
target[key] = source[key];
}
}
}
return target;
};
从源码中我们不难看出,Object.assign方法不对深层对象进行拷贝,且不拷贝继承式的属性
// 用法:Object.assign(target, ...sources)
// target:目标对象,后续所有的对象合并都是基于target进行合并
// sources:源对象,可以是多个,会依次从右向左进行覆盖合并
const obj1 = {
name: "nordon",
info: {
msg: 'msg',
innerInfo: {
msg: 'msg'
}
}
};
const obj2 = {
age: 12,
name: 'wy',
info: {
foo: 'bar'
}
};
const newObj = Object.assign(obj1, obj2);
console.log(newObj);// 这里info属性相同,由于是浅拷贝,所以直接覆盖第一层 newObj.info = { foo:bar }
const obj = Object.create({
age: 1 // age 是继承属性
});
const newObj = Object.assign(obj, {name: 'nordon'})
console.log(newObj) // {name: 'nordon'}
浅拷贝的两种方法:
1.使用递归一层一层往外拷贝
2.使用序列化JSON.stringify(JSON.parse(Obj))方法
obj = {
id: 1,
name: 'andy',
data: [{
id: 2,
name: 'lihua'
},{
id: 3,
name: 'xiaoming'
}],
msg: {
age: 18
}
}
o = {}
function deepObj(newobj, oldobj) {
for(k in oldobj) {
//判断我们的属性值属于哪种数据类型
//1.获取属性值
var item = oldobj[k]
if(item instanceof Array) { //2.判断这个属性值是否是数组
newobj[k] = []
deepObj(newobj[k], item)
} else if (item instanceof Object) { //3.判断这个属性值是否是对象
newobj[k] = {}
deepObj(newobj[k], item)
} else { //4.属于简单数据类型
newobj[k] = item
}
}
}
deepObj(o, obj)
let newObj = JSON.parse(JSON.stringify(obj));
console.log(o,newObj );
writable(可写)
、 enumerable(可枚举)
、 configurable(可配置)
。
let myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true, 决定是否可以修改属性的值。
// enumerable: true, 决定这个描述符控制的是属性是否会出现在对象的属性枚举中。
// configurable: true 决定是否可配置对象的值。
// }可以看到属性描述符的默认值
在创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty(..)
来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。
举例来说:
let myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2
结合 writable:false
和 configurable:false
就可以创建一个真正的常量属性(不可修改、 重定义或者删除):
let myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );
如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.prevent Extensions(..)
:
let myObject = {
a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
Object.seal(..)
会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..)
并把所有现有属性标记为 configurable:false
。
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。
Object.freeze(..)
会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..)
并把所有“数据访问”属性标记为 writable:false
,这样就无法修改它们的值。
这个方法是你可以应用在对象上的级别最高的不可变性
,它会禁止对于对象本身及其任意直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。
你可以“深度冻结”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(..)
, 然后遍历它引用的所有对象并在这些对象上调用 Object.freeze(..)
。但是一定要小心,因为这样做有可能会在无意中冻结其他(共享)对象。
在JS语言规范中,myObject.a
在 myObject
上实际上是实现了 [[Get]]
操作(有点像函数调 用:[[Get]]()
)。对象默认的内置 [[Get]]
操作首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。
然而,如果没有找到名称相同的属性,按照 [[Get]]
算法的定义会执行另外一种非常重要的行为遍历可能存在的 [[Prototype]] 链, 也就是原型链。如果无论如何都没有找到名称相同的属性,那 [[Get]]
操作会返回值 undefined
[[put]] 被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)。 如果已经存在这个属性,[[put]] 算法大致会检查下面这些内容。
对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取。 在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法 应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏 函数,会在设置属性值时调用。 当你给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和 writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。
var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
};
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
{ // 描述符
// 给 b 设置一个 getter
get: function(){
return this.a * 2
},
// 确保 b 会出现在对象的属性列表中
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4
无论是对象文字语法中的get a(){..}
,还是 defineProperty(..)
中的显式定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值。
为了让属性更合理,还应当定义 setter,和你期望的一样,setter 会覆盖单个属性默认的 [put] 操作。通常来说 getter 和 setter 是成对出现的(只定义一个的话 通常会产生意料之外的行为):
var myObject = {
// 给 a 定义一个 getter
get a() {
return this._a_;
},
// 给 a 定义一个 setter
set a(val) {
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a; // 4
for..in
循环可以用来遍历对象的可枚举属性列表(包括 [[Prototype]] 链)。forEach(..)
、every(..)
、some(..)
for..of
,循环每次调用 myObject 迭代器对象的 next() 方法时,内部的指针都会向前移动并 返回对象属性列表的下一个值。var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator](); // 使用 ES6 中的符号 Symbol.iterator 来获取对象的 @@iterator 内部属 性。
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }
类、继承、实例化、多态
多态是说父类的通用行为可以被子类用更特殊的行为重写。 多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。
[[Prototype]]
属性都会被赋予一个非空的值。[[Get]]
操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的 [[Prototype]]
链。这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]]
链。如果是后者的话, [[Get]]
操作的返回值是 undefined
。for..in
遍历对象时,使用 in
操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype,所以它包含 JavaScript 中许多通用的功能。
如果 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo = "bar" 会出现的三种情况
var anotherObject = {
a:2
};
var myObject = Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false
myObject.a++; // 隐式屏蔽!
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty( "a" ); // true
尽管 myObject.a++ 看起来应该(通过委托)查找并增加 anotherObject.a 属性,但是别忘了 ++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]] 查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]] 将值 3 赋给 myObject 中新建的屏蔽属性 a,天呐! 修改委托属性时一定要小心。如果想让 anotherObject.a 的值增加,唯一的办法是 anotherObject.a++。
所有的函数默认都会拥有一个名为 prototype
的公有并且不可枚举的属性,它会指向另一个对象:
function Foo() {
// ...
}
Foo.prototype; // { }
var a = new Foo();
Object.getPrototypeOf( a ) === Foo.prototype; // true
new Foo() 只是间接完成我们的目标:一个关联到其他对象的新对象。
function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
// a.constructor 只是通过默认的 [[Prototype]] 委托指向 Foo
Foo.prototype 默认有一个公有并且不可枚举的属性 .constructor,这个属性引用的是对象关联的函数(本例中是 Foo)。此外,我们可以看到通过“构造函数”调用 new Foo() 创建的对象也有一个 .constructor 属性,指向 “创建这个对象的函数”。 实际上 a 本身并没有 .constructor 属性。而且,虽然 a.constructor 确实指向 Foo 函数,但是这个属性并不是表示 a 由 Foo“构造”。实际上,.constructor 引用同样被委托给了 Foo.prototype,而 Foo.prototype.constructor 默认指向 Foo。a.constructor 只是通过默认的 [[Prototype]] 委托指向 Foo,这和“构造”毫无关系。
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype = Object.create( Foo.prototype );
// 注意!现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
这段代码的核心部分就是语句 Bar.prototype = Object.create( Foo.prototype )
。调用 Object.create(..)
会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象(本例中是 Foo.prototype)。
Object.setPrototypeOf(..)
,可以用标准并且可靠的方法来修改对象的 [[Prototype]] 关联。
我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法: // ES6 之前需要抛弃默认的 Bar.prototype Bar.ptototype = Object.create( Foo.prototype ); // ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype );
[[Prototype]] 机制就是存在于对象中的一个内部链接,它会引用其他 对象。 通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototype]] 关联的对象上进行查找。同理,如果在后者中也没有找到需要的 引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。
if (!Object.create) {
Object.create = function(o) {
function F(){}
F.prototype = o;
return new F();
}; }
如果要访问对象中并不存在的一个属性,[[Get]] 操作就会查找对象内部[[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的作用域链),在查找属性时会对它进行遍历。 所有普通对象都有内置的 Object.prototype,指向原型链的顶端(比如说全局作用域),如 果在原型链中找不到指定的属性就会停止。toString()、valueOf() 和其他一些通用的功能 都存在于 Object.prototype 对象上,因此语言中所有的对象都可以使用它们。 关联两个对象最常用的方法是使用 new 关键词进行函数调用,在调用的 4 个步骤(第 2 章)中会创建一个关联其他对象的新对象。 使用 new 调用函数时会把新对象的 .prototype 属性关联到“其他对象”。带 new 的函数调用 通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。 虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很相似,但 是 JavaScript 中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的 [[Prototype]] 链关联的。 出于各种原因,以“继承”结尾的术语(包括“原型继承”)和其他面向对象的术语都无 法帮助你理解 JavaScript 的真实机制(不仅仅是限制我们的思维模式)。
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
委托模型(对象关联风格)
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
在软件架构中你可以选择是否使用类和继承设计模式。大多数开发者理所当然地认为类是 唯一(合适)的代码组织方式,但是本章中我们看到了另一种更少见但是更强大的设计模式:行为委托。 行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。也就是说,我们可以选择在 JavaScript 中努 力实现类机制(参见第 4 和第 5 章),也可以拥抱更自然的 [[Prototype]] 委托机制。 当你只用对象来设计代码时,不仅可以让语法更加简洁,而且可以让代码结构更加清晰。 对象关联(对象之前互相关联)是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于 [[Prototype]] 的行为委托非常自然地实现。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有