JavaScript总结【2】
Object(对象)基础
通过使用带有可选 属性列表 的花括号 {…}
来创建对象。一个属性就是一个键值对(“key: value”),其中键(key
)是一个字符串(也叫做属性名),值(value
)可以是任何值。
对象属性
属性命名
- 属性名可以是任何字符串或者 symbol,其他类型自动转换为字符串
- **属性名简写:**部分情况下省略
:...
- **属性存在测试:**使用
in
操作符
属性调用
- **点符号:**点符号要求
key
是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用$
和_
) - 方括号:方括号中的字符串必须放在引号中,方括号中变量
key
可以是程序运行时计算得到的,也可以是根据用户的输入得到的,点符号不行
计算属性
当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做 计算属性,如let bag = { [fruit + 'Computers']: 5}
对象引用和复制
引用及比较
地址,两对象===
必须在地址相同为统一对象时成立
克隆与合并
-
Object.assign
Object.assign(dest, [src1, src2, src3...])
将dest
对象与src
合并,改变了dest
对象并且返回值也为合并的结果!- 注意,只拷贝了一层,即多层引用无法复制,进行克隆时可传入空对象。
- 存在多个同名属性时后面的会覆盖前面的
-
深拷贝:递归实现
- 递归
- 使用现有库
// 造轮子
function deepClone (sourceObj, targetObj) {
let cloneObj = targetObj || {}
if(!sourceObj || typeof sourceObj !== "object" || sourceObj.length === undefined){
return sourceObj
}
if(sourceObj instanceof Array){
cloneObj = sourceObj.concat()
} else {
for(let i in sourceObj){
if (typeof sourceObj[i] === 'object') {
cloneObj[i] = deepClone(sourceObj[i], {})
} else {
cloneObj[i] = sourceObj[i]
}
}
}
return cloneObj
}
// lodash函数库
// npm install lodash
import lodash from 'lodash'
let targetOj = lodash.cloneDeep(sourceObj)
JavaScript垃圾回收机制
可达性分析
”可达“值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
基本步骤
垃圾回收的基本算法被称为 “mark-and-sweep”。定期执行以下**“垃圾回收”步骤:**
- 垃圾收集器找到所有的根,并“标记”(记住)它们。
- 然后它遍历并“标记”来自它们的所有引用。
- 然后它遍历标记的对象并标记它们的引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象
- ……如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除
javascriptInfo 垃圾回收机制
优化算法
- 分代收集:辈分越高,检查频次越低
- 增量收集:CMS垃圾回收机制,三色标记原理,分部分处理
- 闲时收集:如其名
对象方法中的this
- 谁调用指向谁
- 箭头函数上下文
对象的构造函数和操作符new
构造函数
**构造函数:**技术所就是常规的函数,但我们预定:
- 命名以大写字母开头
- 它们只能由
new
操作符来执行
new操作符
-
new
执行时的步骤:- 一个新的空对象被创建并分配给
this
- 函数体执行。通常它会修改
this
,为其添加新的属性 - 返回
this
的值
- 一个新的空对象被创建并分配给
// 过程类似于:
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
- 检测是否使用了
new
调用函数
可以在函数内部使用new.target
判断是否使用new
调用的
可选链?.
首先在此约定对象存在即非undefined
、非null
可选链是什么
**什么是可选链:**它不是运算符,而是一种特殊语法结构
// 这个用户信息有没有地址?有没有街道?
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)
与&&
相比可以少些几遍属性名
注意:使用钱必须声明变量,不能过度使用,其同样具有短路效应(不存在时就短路了)
可选链的变体
- 将
?.()
用于调用一个可能不存在的函数 - 与前面一样,但不使用点符号,而使用方括号访问属性,即使用
?[]
- 与
delete
一起使用,如delete user?.name; // 如果 user 存在,则删除 user.name
注意:可以用来删除,但不能用来写入!
Symbol类型
什么是symbol
“Symbol” 值表示唯一的标识符,它不会自动转换为字符串。
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
获取symbol描述的属性
使用sym.description
即可
创建隐藏属性
let user = { // 属于另一个代码
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 使用 Symbol 作为键来访问数据
在对象中使用symbol作为字面量
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 而不是 "id":123
};
symbol的键
- symbol 在
for…in
中会被跳过 Object.keys(user)
也会忽略它- 但
Object.assign(...)
能复制到它
全局symbol
Symbol.for(key)
可以在全局注册表中读取symbol不存在则创建它Symbol.for(key)
完全相反,通过symbolf返回symbol内的名字(书写时哪个字符串),未找到返回undedined
系统Symbol
以下都是symbol对象
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
对象的原始值转换
对象原始值三种转换
- boolean:所有的对象在布尔上下文(context)中均为
true
,哪怕是空对象。所以对于对象,不存在 to-boolean 转换,只有字符串和数值转换。 - number:数值转换发生在对象相减或应用数学函数时。例如,
Date
对象(将在 日期和时间 一章中介绍)可以相减,date1 - date2
的结果是两个日期之间的差值 - string:至于字符串转换 —— 通常发生在我们像
alert(obj)
这样输出一个对象和类似的上下文中
ToPrimitive
- 转换调用顺序—>转换能得到什么
- 调用
obj[Symbol.toPrimitive](hint)
—— 带有 symbol 键Symbol.toPrimitive
(系统 symbol)的方法,如果这个方法存在的话 - 否则,如果 hint 是
"string"
—— 尝试obj.toString()
和obj.valueOf()
,无论哪个存在。 - 否则,如果 hint 是
"number"
或"default"
—— 尝试obj.valueOf()
和obj.toString()
,无论哪个存在
- Symbol.toPrimitive
Symbol.toPrimitive
是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
const object1 = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 42;
}
return null;
}
};
console.log("x"+object1); // expected output: 42
console.log("x"+object1); // expected output: "xnull"
- toString和valueOf
// 只要你进行了转换,对象没有实现Symbol.toPrimitive他就会调用,可以重写它
let user = {
name: "John",
money: 1000,
// 对于 hint="string"
toString() {
return `{name: "${this.name}"}`;
},
// 对于 hint="number" 或 "default"
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
对象属性配置
- 常用属性标志
以下默认均为true
writable
— 如果为true
,则值可以被修改,否则它是只可读的。enumerable
— 如果为true
,则会被在循环中列出,否则不会被列出。configurable
— 如果为true
,则此特性可以被删除,这些属性也可以被修改,否则不可以
-
获取和修改属性标志
-
获取:
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
Object.getOwnPropertyDescriptors(obj)
:允许一次获取全部属性
-
修改:
-
Object.defineProperty(obj, propertyName, descriptor)
-
Object.defineProperties()
:允许一次定义多个属性
-
-
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
访问器属性
get 和 set 访问器属性
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// set fullName 将以给定值执行
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
注意:一个属性要么是访问器(具有 get/set
方法),要么是数据属性(具有 value
),但不能两者都是
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
更聪明的 getter 和 setter
Getter/setter 可以用作“真实”属性值的包装器,以便对它们进行更多的控制
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Name 太短了……
注意:从技术上讲,外部代码可以使用 user._name
直接访问 name。但是,这儿有一个众所周知的约定,即以下划线 "_"
开头的属性是内部属性,不应该从对象外部进行访问。
访问器属性的重要作用
兼容性,它们允许随时通过使用 getter 和 setter 替换“正常的”数据属性,来控制和调整这些属性的行为。
// 加入原来我们的User对象是这样的
function User(name, age) {
this.name = name;
this.age = age;
}
// 现在我们想存储birthdaty,而不是age
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
// 添加访问器属性改进
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// 年龄是根据当前日期和生日计算得出的
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday 是可访问的
alert( john.age ); // ……age 也是可访问的