对象 创建对象的两种方式:
1 2 3 4 5 let user = new Object (); let user = { name : "Tom" , age : 20 , };
属性的访问和设置值,删除变量,有两种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 user.name = "Jim" ; console .log (user.name ); delete user.name ;user["name" ] = "jim" ; console .log (user["name" ]);delete user["name" ];const key = "name" ;user[key] = "Jimmy" ; console .log (user[key])console .log (user["na" + "me" ]);
属性值的限制和简写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const user = { name : "Tom" , "come from" : "China" , age : 22 } user.com from = "New name" ; 但可以使用方括号的方式: user["come from" ] = "US" ; const key = "come from" ;console .log (user[key]);const user = { for : 123 }; const name = "Tom" ;const age = 22 ;const user = { name :name, age :age }; const user = { name, age, }; function makeUser (name, age ) { return { name, age } }
循环获取对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let user = { name : "Tom" , 5 : 55555 , age : 10 } for (let k in user) { console .log (k); } console .log ("name" in user);
引用和复制 对象的值是引用。
1 2 3 4 5 6 7 8 9 10 11 12 let user1 = { name : "Tom" , age : 10 , }; let user2 = user1;user2.age = 20 ; console .log (user1.age ); console .log (user1 == user2);
如果要通过赋值,将两个对象分来,就需要使用克隆。javascript内置有Object.assign,这这个克隆不够深:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let user1 = { name : "Tom" , age : 10 , child :{ name : "apple" } }; let clone = {};Object .assign (clone, user1);user1.name = "Jimmy" ; user1.child .name = "banana" ; console .log (clone);
真正的深层克隆,可用 JavaScript 库 lodash 中的 _.cloneDeep(obj) 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 _ = require ('lodash' ); let user1 = { name : "Tom" , age : 10 , child :{ name : "apple" } }; let clone = {};clone = _.cloneDeep (user1); user1.name = "Jimmy" ; user1.child .name = "banana" ; console .log (clone);
对象方法 可以给对象创建方法,类似其它语言的 class method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let user = { name : "Tom" , age : 22 , } user.sayHello = function ( ) { console .log ("hello" ); } user.sayHello (); hello = function ( ) { console .log ("hello" ); } user.sayHello = hello; user.sayHello ();
类方法可写在类构造时:
1 2 3 4 5 6 7 8 9 10 11 let user = { name : "Tom" , age : 22 , sayHello : function ( ) { console .log ("hello " + this .name ); } } user.sayHello ();
javascript 中的this,比其它语言中的this关键字更加宽范。他指定的内容是在运行时计算出来的。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const user = {name : "Tom" };const admin = {name : "boss" };function sayHello ( ){ console .log ("hello " + this .name ); } user.sayHi = sayHello; admin.sayHi = sayHello; user.sayHi (); admin.sayHi ();
this 并没有严格的与某个对象绑定。在javascript中,this比较自由。
注意:箭头头函数没有 this,如果在箭头函数中引用this,会取来自箭头函数外部的this,如:
1 2 3 4 5 6 7 8 9 let user = { firstName : "Ilya" , sayHi ( ) { let arrow = ( ) => alert (this .firstName ); arrow (); } }; user.sayHi ();
构造器 new 除了常规的 {…}来创建对象,也可以使用构造器。对于要多次创建的对象,用构造器更便捷。构造器函数实际上是个常规函数,有两个约定:
以大写字母开头
只能由 new 来执行
构造器执行时,由三个流程:
创建空对象{},并赋值给 this
执行函数体。
返回this
如:
1 2 3 4 5 6 7 8 9 10 function User (name ) { this .name = name; this .role = "worker" ; } const u1 = new User ("Tom" );console .log (u1);
new 可以让任何函数以这种方式执行。但一般我们用于构建函数时,以大写字母命名,由new执行。
如果任何函数都可以被new来使用,那如何判断这个调用,是否是被new创建的呢?在函数内部,可使用 new.target 函数来测试是否是由new来创建的。如果不是new创建的,由返回undefined;否则,返回函数。
1 2 3 4 5 6 7 8 9 10 11 function User (name ) { if (!new .target ) { return new User (name); } this .name = name; this .role = "worker" ; } const u1 = User ("Tom" );console .log (u1);
构造器的return 构造器一般无 return 语句
如果构造器返回一个对象,则使用这个对象返回,而代替this
如果构造器返回一个原始类型,则忽略。就像没这句一亲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function User1 (name ) { this .name = name; return {name : "foo" } } function User2 (name ) { this .name = name; return "bar" ; } const u1 = new User1 ("Tom" );const u2 = new User2 ("Tom" );console .log (u1); console .log (u2);
可选链 对于一个对象,我们可能会像这样的方式访问其属性值:user.address.city.name 。但有可能其address未设置,而直接访问 user.address.city.name 就会报错,而我们想其返回undefined亦或null.
我们可以在user到name中,从根一步步的判断是否存在,存在再取下一步。这种方式可行,但代码太难看。
在javascript中有个可选链的操作,可优雅的达到此目的,使用方法为 value?.prop,他执行下面的操作:
如果value为undefined或null,则返回undefined
如果value存在,则返回 value.prop
如下:
1 2 3 4 5 6 7 8 9 10 const user = { address : { city : { name : "beijing" } } }; console .log (user?.addr ?.city ?.name ); console .log (user?.address ?.city ?.name );
可选链可用于函数和计算属性:
?.()或 ?.[]
也可用于删除属性:
delete user?.address
总之,可选链 ?. 语法有三种形式:
obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined。
obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined。
obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined。
Symbol 复习一下javascript中的原始类型,共有7种:
string
number
bigint
boolean
symbol
undefined
null
Symbol 的值是一个唯一的标识符,每次调用时不同。他在内部是不同的值,但在打印时,他的默认的 toString() 方法只展示其信息和id标识,并不显示唯一的标识信息值。如:
1 2 3 4 5 6 7 8 9 10 11 const id1 = Symbol ("id" );const id2 = Symbol ("id" );console .log (id1); console .log (id1 == id2); const id = Symbol ("id desc" );console .log (id); console .log (id.toString ()); console .log (id.description );
Symbol 的作用 Symbol 的最大作用,是允许我们创建对象的隐藏属性。比如我们引用第三方代码中的user对象,但我们想添加个标识符,又不想破坏原有代码,我们就可以起一个特殊的键来存放我们的属性值,而原有代码因不知道这个键,而不会感知到我们添加了内容。同时,Symbol对象在迭代时,会被忽略。
如:
1 2 3 4 5 6 7 8 let user = { name : "Tom" , }; gender = Symbol ("id" ); user.gender = "male" ; console .log (user.gender );
在迭代时,Symbol对象会被忽略,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const id1 = Symbol ("id1" );let user = { name : "Tom" , [id1]: "333" } const id2 = Symbol ("id2" );user[id2] = id2; console .log (Object .keys (user)); for (let k in user) { console .log (k); }
当克隆一个对象时, Symbol 会被克隆:
1 2 3 4 5 6 7 8 9 let user = { name : "Tom" , [Symbol ("id" )]: "333" } let clone = {};Object .assign (clone, user);console .log (clone);
全局 symbol 系统中有个全局 Symbol 符号表。当通过 Symbol.for(key)来创建Symbol时,他会创建并返回一个Symbol对象,同时记录在全局符号表中。当符号表中存在同名的Symbol时,则会返回已存在的Symbol。
反之,也可通过 Symbol.keyFor(sym:symbol)来把Symbol转换为标识符。
1 2 3 4 5 6 7 8 9 let s1 = Symbol .for ("id" );let s2 = Symbol .for ("id" );let s3 = Symbol .for ("id3" );console .log (s1 == s2); console .log (s1 == s3); const sss = Symbol ("symbol not exists" );console .log (Symbol .keyFor (s1)); console .log (Symbol .keyFor (sss));
对象和原始值 JavaScript 允许我们像使用对象一样使用原始类型。
原始类型是一个原始值,就是7种原始类型中的任何一种的值。
对象,可以存储多个属性;可以使用{…}的方式创建且可以包含函数
原始类型,有很多的方法,而为了实现这些操作方法,内部通过包装器来实现。而包装器只在使用期间存在,用后即毁。如:
1 2 3 const str = "Hello world!" ;console .log (str.toUpperCase ());
在访问字符串的 toUpperCase方法时,
str是个原始值。访问其方法,需要创建一个包装对象,此对象有其用到的方法
包装器的方法被执行,将返回值给调用者。
中间的包装器对象销毁
注意:原始类型的构造器仅供内部使用,自己的代码中不要使用,容易出现问题。而不在new的构造方法可以使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 console .log (`typeof new Number(0): ${typeof new Number (0 )} ` );console .log (`typeof Number(0): ${typeof Number (0 )} ` );if (new Number (0 )) { console .log ("new Number(0) is TRUE" ); } else { console .log ("new Number(0) is false" ); } if ( Number (0 )) { console .log (" Number(0) is TRUE" ); } else { console .log (" Number(0) is false" ); }
undefined和null没包装器,也没任何方法。