JavaScript拓展(1)


每日一记

注:本章节较难,请选择性阅读

对象

使用{…}来创建对象。一个属性就是一个键值对(“key: value”),其中键是一个字符串(也叫做属性名),值可以是任何值。

构造对象方法:
let user = new Object();     // “构造函数” 的语法
let user = {};               // “字面量” 的语法

读取文件的属性:
alert( user.name );          // John
alert( user.age );           // 30

移除属性:
delete user.age;             // 移除age这个key

方括号:
let user = {};
// 设置
user["likes birds"] = true;  // 单引号或双引号都可以
// 读取
alert(user["likes birds"]);  // true
// 删除
delete user["likes birds"];

属性值简写:
name, 与 name: name 相同

属性名称限制:
类型会被自动地转换为字符串
但__proto__属性。不能将它设置为一个非对象的值

属性存在性测试:
alert( user.noSuchProperty === undefined );       // true 意思是没有这个属性
alert( "age" in user );                           // true 存在

for..in 循环

for (key in object) {
  // 对此对象属性中的每个键执行的代码
}
遍历一个对象时,整数属性会被进行从小到大排序
但是 “+49” 和 “1.2” 不行,那它们就按照创建时的顺序来排序

对象引用和复制

赋值了对象的变量存储的不是对象本身,而是该对象“在内存中的地址”。

对象的引用:
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete';               // 通过 "admin" 引用来修改
alert(user.name);                  // 'Pete',修改能通过 "user" 引用看到

仅当两个对象为同一对象时,两者才相等。
两个独立的对象则并不相等,即使它们看起来很像(都为空)。

对象的复制

通过遍历已有对象的属性,并在原始类型值的层面复制它们,以实现对已有对象结构的复制。

let user = {
  name: "John",
};
let clone = {};                       // 新的空对象
for (let key in user) {               // 将 user 中所有的属性拷贝到其中
  clone[key] = user[key];             // 现在 clone 是带有相同内容的完全独立的对象
}
clone.name = "Pete";                  // 改变了其中的数据
alert( user.name );                   // 原来的对象中的 name 属性依然是 John

【也可以使用 Object.assign 方法】

Object.assign(dest, [src1, src2, src3...])
1.dest 是指目标对象
2.src1, ..., srcN是源对象(需传递的参数)
3.将所有源对象的属性拷贝到目标对象 dest 中
4.调用结果返回 dest
5.如果被拷贝的属性的属性名已经存在,那么它会被覆盖
深层克隆
let 克隆生成的对象 = Object.assign({}, 克隆的对象);
或者使用“深拷贝”函数,例如 _.cloneDeep(obj)

垃圾回收

1.可达性
  如果一个值可以通过引用链从根访问任何其他值,则认为该值是可达的
  垃圾回收器会删除掉那些已经不可达的(没有外部对其的引用)
2.内部算法(mark-and-sweep):
  垃圾收集器遍历所有的根,并“标记”(记住)它们和他们的引用。
  没有被标记的对象都会被删除。
3.优化建议:
  分代收集/增量收集/闲时收集
  进阶了解

对象方法

存储在对象属性中的函数被称为“方法”
使用函数表达式创建一个函数,并将其指定给对象的属性,随后调用它。就叫做得到了 user 对象的 sayHi 方法。

this

访问对象中存储的信息
this可以用于任何函数,它取决于代码上下文
严格模式下没有对象的情况下调用:this==undefined但尝试访问this.name将会报错
this的值是在调用时计算出来的,它的值取决于在“点符号前”的是什么对象。
箭头函数没有自己的this。若在箭头函数中引用this,则this值取决于外部“正常的”函数。

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;

// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)

admin['f'](); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)

重点误区,必须记住

构造器和操作符 “new”

构造函数

命名以大写字母开头。
只能由 “new” 操作符来执行

function User(name) {                1.一个新的空对象被创建并分配给 this。
  this.name = name;                  2.函数体执行。通常它会修改 this,为其添加新的属性。
  this.isAdmin = false;              3.返回 this 的值。
}

任何函数(除了箭头函数,它没有自己的 this)都可以用作构造器

new function() { … }

如果有许多行用于创建单个复杂对象的代码,可以将它们封装在一个立即调用的构造函数中

// 创建一个函数并立即使用 new 调用它
let user = new function() {
  this.name = "John";
  this.isAdmin = false;
  // ……用于用户创建的其他代码
  // 也许是复杂的逻辑和语句
  // 局部变量等
};

这个构造函数不能被再次调用,因为它不保存在任何地方,只是被创建和调用。
因此,这个技巧旨在封装构建单个对象的代码,而无需将来重用。

构造器模式测试,无需重视

可选链

语法有三种形式:
obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined。
obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined。
obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined。
总结?. 检查左边部分是否为 null/undefined,如果不是则继续运算。

symbol 类型

“symbol” 值表示唯一的标识符。
使用 Symbol() 来创建这种类型的值
可以给 symbol 一个描述(也称为 symbol 名) //let id = Symbol(“id”);
symbol 保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同。symbol 不会被自动转换为字符串
真的想显示一个 symbol,我们需要

在它上面调用 .toString()
    let id = Symbol("id");
    alert(id.toString());        //Symbol(id)
或者获取 symbol.description 属性,只显示描述
    let id = Symbol("id");
    alert(id.description);       // id

主要的使用场景:
1.“隐藏” 对象属性。
如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个 symbol 并使用它作为属性的键。
2.JavaScript 使用了许多系统 symbol,这些 symbol 可以作为 Symbol.* 访问。我们可以使用它们来改变一些内建行为。
3.symbol 在 for…in 中会被跳过
4.Object.keys(user) 也会忽略它们
5.相反,Object.assign 会同时复制字符串和 symbol 属性

对象 —— 原始值转换

JavaScript 不允许自定义运算符对对象的处理方式
此类运算的情况下,对象会被自动转换为原始值,然后对这些原始值进行运算,并得到运算结果(也是一个原始值)

转换规则

1.没有转换为布尔值,所有的对象在布尔上下文(context)中均为 true
2.数字转换发生在对象相减或应用数学函数时
3.字符串转换通常发生在像 alert(obj) 这样输出一个对象和类似的上下文中

hint

“string”(对于 alert 和其他需要字符串的操作)
“number”(对于数学运算)
“default”(少数运算符,通常对象以和 “number” 相同的方式实现 “default” 转换)

Symbol.toPrimitive

用来给转换方法命名

toString/valueOf

对于 “string” hint:调用 toString 方法,如果它不存在,则调用 valueOf 方法(因此,对于字符串转换,优先调用 toString)。
对于其他 hint:调用 valueOf 方法,如果它不存在,则调用 toString 方法(因此,对于数学运算,优先调用 valueOf 方法)。

转换可以返回任何原始类型

它们不一定会返回 “hint” 的原始值
唯一强制性的事情是:这些方法必须返回一个原始值,而不是对象。

进一步的转换

如果我们将对象作为参数传递,则会出现两个运算阶段:
1.对象被转换为原始值(通过前面我们描述的规则)。
2.如果还需要进一步计算,则生成的原始值会被进一步转换。

总结:

首先调用 obj[Symbol.toPrimitive](hint) 如果这个方法存在,
否则,如果 hint 是 "string"
        尝试调用 obj.toString() 或 obj.valueOf(),无论哪个存在。
否则,如果 hint 是 "number" 或者 "default"
        尝试调用 obj.valueOf() 或 obj.toString(),无论哪个存在。

在实际使用中,通常只实现 obj.toString() 作为字符串转换的“全能”方法就足够了,该方法应该返回对象的“人类可读”表示,用于日志记录或调试。

每日一句

遇事不决可问春风,春风不语即随本心。

评论
  目录