前端技术学习路线图-初阶-JavaScript-对象

对象

Object 是 JavaScript 的一种数据类型。它用于存储各种键值集合和更复杂的实体。可以通过 Object() 构造函数或者使用对象字面量的方式创建对象。

在 JavaScript 中,几乎所有的对象都是 Object 的实例;一个典型的对象从 Object.prototype 继承属性(包括方法),尽管这些属性可能被覆盖(或者说重写)。唯一不从 Object.prototype 继承的对象是那些 null 原型对象,或者是从其他 null 原型对象继承而来的对象。

通过原型链,所有对象都能观察到 Object.prototype 对象的改变,除非这些改变所涉及的属性和方法沿着原型链被进一步重写。尽管有潜在的危险,但这为覆盖或扩展对象的行为提供了一个非常强大的机制。为了使其更加安全,Object.prototype 是核心 JavaScript 语言中唯一具有不可变原型的对象——Object.prototype 的原型始终为 null 且不可更改。

对象使用

对象声明语法:

let 对象名 = {}

let 对象名 = new Object()

实际开发中,多用花括号。{}是对象字面量。

对象由属性和方法组成:

let 对象名 = {
        属性名:属性值,
        方法名:函数
      }
  • 属性:信息或特征(名词)。比如手机尺寸、颜色、重量等等。

  • 方法:功能或行为(动词)。比如手机打电话、发信息、玩游戏等等。

属性:

数据描述的信息称为属性。

  • 属性都是成对出现的,包括属性名和值,它们之间使用英文:隔开。

  • 多个属性之间使用英文,隔开。

  • 属性就是依附在对象上的变量(外面是变量,对象内是属性)。

  • 属性名可以使用””或‘’,一般情况下省略,除非名称遇到特殊符号如空格、中横线等。

方法:

数据行为性的信息称为方法,其本质是函数。

  • 方法是由方法名和函数两部分构成,它们之间使用英文:隔开。

  • 多个方法之间使用英文,隔开。

  • 方法是依附在对象中的函数。

  • 方法名可以使用””或‘’,一般情况下省略,除非名称遇到特殊符号如空格、中横线等。

对象中的方法:

  • 声明对象,并添加了若干方法后,可以使用。调用对象中的函数,称之为方法调用。

  • 也可以添加形参和实参。

  • 注意:千万别忘了给方法名后面加小括号。

操作对象属性

对象本质是无序的数据集合,操作数据无非是增、删、改、查语法:

属性-查:

  • 声明对象,并添加了若干属性后,可以使用 . 获得对象中属性对应的值,称之为属性访问。

  • 语法:

对象名.属性名 =
  • 简单理解就是获得对象里面的属性值。

  • 查的另外一种方法:当属性名存在多词或者-等属性时,用对象[‘属性’]方式,单引号或双引号都可以。

对象名['属性名'] =

属性-改:

  • 语法:
对象名.属性名 = 新值

属性-增:

  • 语法:
对象名.新属性名 = 新值

属性-删:

  • 语法:
delete 对象名.属性名

注:改和增的语法一样,判断标准就是对象有没有这个属性,没有就是增,有就是改。

in 、for…in

in

如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true

const car = { make: 'Honda', model: 'Accord', year: 1998 };

console.log('make' in car);
// Expected output: true

delete car.make;
if ('make' in car === false) {
  car.make = 'Suzuki';
}

console.log(car.make);
// Expected output: "Suzuki"
for…in

for...in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。

for (variable in object)
  statement

variable 在每次迭代时,variable 会被赋值为不同的属性名。

object Symbol 类型的可枚举属性被迭代的对象。

var obj = {a:1, b:2, c:3};

for (var prop in obj) {
  console.log("obj." + prop + " = " + obj[prop]);
}

// Output:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

for ... in是为遍历对象属性而构建的,不建议与数组一起使用,数组可以用Array.prototype.forEach()for ... of,那么for ... in的到底有什么用呢?

它最常用的地方应该是用于调试,可以更方便的去检查对象属性(通过输出到控制台或其他方式)。尽管对于处理存储数据,数组更实用些,但是你在处理有key-value数据(比如属性用作“键”),需要检查其中的任何键是否为某值的情况时,还是推荐用for ... in

对象引用

一个 javascript 对象有很多属性。一个对象的属性可以被解释成一个附加到对象上的变量。对象的属性和普通的 javascript 变量基本没什么区别,仅仅是属性属于某个对象。属性定义了对象的特征。你可以通过点符号来访问一个对象的属性。

objectName.propertyName

和其他 javascript 变量一样,对象的名字 (可以是普通的变量) 和属性的名字都是大小写敏感的。你可以在定义一个属性的时候就给它赋值。例如,我们创建一个 myCar 的对象然后给他三个属性,make,model,year。具体如下所示:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;

对象中未赋值的属性的值为 undefined(而不是 null)。

myCar.noProperty; // undefined

JavaScript 对象的属性也可以通过方括号访问或者设置(更多信息查看 property accessors). 对象有时也被叫作关联数组,因为每个属性都有一个用于访问它的字符串值。例如,你可以按如下方式访问 myCar 对象的属性:

myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;

一个对象的属性名可以是任何有效的 JavaScript 字符串,或者可以被转换为字符串的任何类型,包括空字符串。然而,一个属性的名称如果不是一个有效的 JavaScript 标识符(例如,一个由空格或连字符,或者以数字开头的属性名),就只能通过方括号标记访问。这个标记法在属性名称是动态判定(属性名只有到运行时才能判定)时非常有用。例如:

// 同时创建四个变量,用逗号分隔
var myObj = new Object(),
    str = "myString",
    rand = Math.random(),
    obj = new Object();

myObj.type              = "Dot syntax";
myObj["date created"]   = "String with space";
myObj[str]              = "String value";
myObj[rand]             = "Random Number";
myObj[obj]              = "Object";
myObj[""]               = "Even an empty string";

console.log(myObj);

请注意,方括号中的所有键都将转换为字符串类型,因为 JavaScript 中的对象只能使用 String 类型作为键类型。例如,在上面的代码中,当将键 obj 添加到 myObj 时,JavaScript 将调用 obj.toString() 方法,并将此结果字符串用作新键。

你也可以通过存储在变量中的字符串来访问属性:

var propertyName = "make";
  myCar[propertyName] = "Ford";

  propertyName = "model";
  myCar[propertyName] = "Mustang";

深浅拷贝

浅拷贝

浅拷贝 指的就是拷贝复制目标数据,生成一个新数据。

目标数据 ==> 基本数据类型 ==> 直接拷贝目标数据的值。

目标数据 ==> 引用数据类型 ==> 只拷贝第一层数据,如果第一层 数据 有引用类型的值 则直接拷贝地址。由于拷贝的是地址,指向的是 同一个对象 数据之间的修改会互相影响。

var obj = {
    age: 18,
    nature: ['smart', 'good'], // 由于拷贝的地址 都指向同一个 对象 
  														 // 所以 数据之间会互相影响
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}

var newObj = {...obj}
obj.age = 111
obj.nature.push(12312)

实现浅拷贝的方法:

  • Object.assign
var obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    love: function () {
        console.log('fx is a great girl')
    }
}
var newObj = Object.assign({}, obj);
  • 拓展运算符
var obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    }
}

var newObj = {...obj}
  • 手动简单封装
function shallowClone(obj) {
  if(obj instanceof Array) { // 判断一下数据类型 如果是数组
    const newObj = [] // 直接 数组第一层数据
    newObj.push(...obj)
    return newObj
  } else if (obj instanceof Object) { // 判断一下数据类型 如果是数组
    const newObj = {};
    for(let prop in obj) { // 拷贝一下 对象的第一层数据
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
  } else {
    return obj
  }
}
深拷贝

在进行深拷贝时,会拷贝所有的属性,并且如果这些属性是对象,也会对这些对象进行深拷贝,直到最底层的基本数据类型为止。这意味着,对于深拷贝后的对象,即使原对象的属性值发生了变化,深拷贝后的对象的属性值也不会受到影响。

var obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    }
}

实现方式:

  • _.cloneDeep()==> loadsh 提供的*
  • jQuery.extend()
  • JSON.parse(JSON.stringify()) ===> 会忽略undefined、symbol和函数 *
  • 手写循环递归

JSON.parse(JSON.stringify())

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

手写一个简单的循环递归

function deepCopy(target) {
  // 当检测目标为 基本数据类型、null 时,直接返回(首次判断及递归时的拦截)
  if ( 
      typeof target !== 'object' || 
      target === null || 
      target instanceof Date || 
      target instanceof RegExp
    ) return target

  // 判断目标时数组还是对象
  const newTarget = Array.isArray(target) ? [] : {}
  

  // 遍历对象(数组则遍历下标)将引用类型递归遍历,基本类型直接赋值到新对象(数组)的对应的键上
  Object.keys(target).forEach(
      (key) => { // 对象的属性 || 数组的索引 ==》 target[key]
        
        // target[key] instanceof Object 判断数据类型 是否 Object
        // 是的话 递归 继续 拷贝对象内的属性
        // 不是的话 拿到属性对应的值 进行赋值
        newTarget[key] = target[key] instanceof Object ? deepCopy(target[key]) : target[key]
      }
  )

  // 返回深拷贝后的对象
  return newTarget
}

垃圾回收机制

什么是垃圾回收机制?

垃圾回收机制(Garbage Collection) 简称 GC。

JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题。

但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况。

不再用到的内存,没有及时释放,就叫做内存泄漏

内存生命周期

JS环境中分配的内存, 一般有如下生命周期:

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存 。
  2. 内存使用:即读写内存,也就是使用变量、函数等 。
  3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存 。
  4. 说明:
    1. 全局变量一般不会回收(关闭页面回收);。
    2. 一般情况下局部变量的值, 不用了, 会被自动回收掉。
垃圾回收算法

​ 所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉

下面介绍两种常见的浏览器垃圾回收算法: 引用计数法标记清除法

引用计数法

IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。

算法:

  1. 跟踪记录每个值被引用的次数。
  2. 如果这个值的被引用了一次,那么就记录次数1。
  3. 多次引用会累加。
  4. 如果减少一个引用就减1。
  5. 如果引用次数是0 ,则释放内存。
let person = {
  age: 18,
}
let p = person
person = 1
p = null

由上面可以看出,引用计数算法是个简单有效的算法。

但它却存在一个致命的问题:嵌套引用

如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。

function fn() {
  let o1 = {}
  let o2 = {}
  o1.a = o2
  o2.a = o1
  return '引用计数无法回收'
}
fn()

因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。

标记清除法

现代的浏览器已经不再使用引用计数算法了。

现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

核心:

  1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
  2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

new

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car('Eagle', 'Talon TSi', 1993);

console.log(car1.make);
// Expected output: "Eagle"
语法:
new constructor[([arguments])]

constructor 一个指定对象实例的类型的类或函数。

arguments 一个用于被 constructor 调用的参数列表。

描述

new 关键字会进行如下的操作:

  1. 创建一个空的简单 JavaScript 对象(即 {});
  2. 为步骤 1 新创建的对象添加属性 __proto__,将该属性链接至构造函数的原型对象;
  3. 将步骤 1 新创建的对象作为 this 的上下文;
  4. 如果该函数没有返回对象,则返回 this

创建一个用户自定义的对象需要两步:

  1. 通过编写函数来定义对象类型。
  2. 通过 new 来创建对象实例。

创建一个对象类型,需要创建一个指定其名称和属性的函数;对象的属性可以指向其他对象,看下面的例子:

当代码 new Foo(…) 执行时,会发生以下事情:

  1. 一个继承自 Foo.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

你始终可以对已定义的对象添加新的属性。例如,car1.color = “black” 语句给 car1 添加了一个新的属性 color,并给这个属性赋值 “black”。但是,这不会影响任何其他对象。要将新属性添加到相同类型的所有对象,你必须将该属性添加到 Car 对象类型的定义中。

你可以使用 Function.prototype 属性将共享属性添加到以前定义的对象类型。这定义了一个由该函数创建的所有对象共享的属性,而不仅仅是对象类型的其中一个实例。下面的代码将一个值为 null 的 color 属性添加到 car 类型的所有对象,然后仅在实例对象 car1 中用字符串 “black” 覆盖该值。

function Car() {}
car1 = new Car();
car2 = new Car();

console.log(car1.color);    // undefined

Car.prototype.color = "original color";
console.log(car1.color);    // original color

car1.color = 'black';
console.log(car1.color);   // black

console.log(car1.__proto__.color) //original color
console.log(car2.__proto__.color) //original color
console.log(car1.color)  // black
console.log(car2.color) // original color

可选链运算符(?.)

可选链运算符(?.) 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 运算符的功能类似于 . 链式运算符,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。

当尝试访问可能不存在的对象属性时,可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链运算符也是很有帮助的。

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);
// Expected output: undefined

console.log(adventurer.someNonExistentMethod?.());
// Expected output: undefined
描述

通过连接的对象的引用或函数可能是 undefinednull 时,可选链运算符提供了一种方法来简化被连接对象的值访问。

比如,思考一个存在嵌套结构的对象 obj。不使用可选链的话,查找一个深度嵌套的子属性时,需要验证之间的引用,例如:

let nestedProp = obj.first && obj.first.second;

为了避免报错,在访问obj.first.second之前,要保证 obj.first 的值既不是 null,也不是 undefined。如果只是直接访问 obj.first.second,而不对 obj.first 进行校验,则有可能抛出错误。

有了可选链运算符(?.),在访问 obj.first.second 之前,不再需要明确地校验 obj.first 的状态,再并用短路计算获取最终结果:

let nestedProp = obj.first?.second;

通过使用 ?. 运算符取代 . 运算符,JavaScript 会在尝试访问 obj.first.second 之前,先隐式地检查并确定 obj.first 既不是 null 也不是 undefined。如果obj.firstnull 或者 undefined,表达式将会短路计算直接返回 undefined

这等价于以下表达式,但实际上没有创建临时变量:

let temp = obj.first;
let nestedProp = ((temp === null || temp === undefined) ? undefined : temp.second);

Symbol.toPrimitive

Symbol.toPrimitive 是内置的 symbol 属性,其指定了一种接受首选类型并返回对象原始值的表示的方法。它被所有的强类型转换制算法优先调用。

const object1 = {
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return 42;
    }
    return null;
  }
};

console.log(+object1);
// Expected output: 42

Symbol.toPrimitive 属性(用作函数值)的帮助下,对象可以转换为一个原始值。该函数被调用时,会被传递一个字符串参数 hint,表示要转换到的原始值的预期类型。hint 参数的取值是 "number""string""default" 中的任意一个。

getters、setters

getters

get 语法将对象属性绑定到查询该属性时将被调用的函数。

const obj = {
  log: ['a', 'b', 'c'],
  get latest() {
    return this.log[this.log.length - 1];
  }
};

console.log(obj.latest);
// Expected output: "c"

语法:

{get prop() { ... } }

{get [expression]() { ... } }

prop 要绑定到给定函数的属性名。

expression 从 ECMAScript 2015 开始,还可以使用一个计算属性名的表达式绑定到给定的函数。

描述:

有时需要允许访问返回动态计算值的属性,或者你可能需要反映内部变量的状态,而不需要使用显式方法调用。在 JavaScript 中,可以使用 getter 来实现。

尽管可以结合使用 getter 和 setter 来创建一个伪属性,但是不可能同时将一个 getter 绑定到一个属性并且该属性实际上具有一个值。

使用get语法时应注意以下问题:

  • 可以使用数值或字符串作为标识;
  • 必须不带参数(请参考Incompatible ES5 change: literal getter and setter functions must now have exactly zero or one arguments);
  • 它不能与另一个 get 或具有相同属性的数据条目同时出现在一个对象字面量中(不允许使用 { get x() { }, get x() { } } 和 { x: …, get x() { } })。
setter

当尝试设置属性时,set 语法将对象属性绑定到要调用的函数。它还可以在类中应用。

const language = {
  set current(name) {
    this.log.push(name);
  },
  log: []
};

language.current = 'EN';
language.current = 'FA';

console.log(language.log);
// Expected output: Array ["EN", "FA"]

语法:

{ set prop(val) { /* … */ } }
{ set [expression](val) { /* … */ } }

prop 要绑定到给定函数的属性名。

val 用于保存尝试分配给prop的值的变量的一个别名。

描述:

在 javascript 中,如果试着改变一个属性的值,那么对应的 setter 将被执行。setter 经常和 getter 连用以创建一个伪属性。不可能在具有真实值的属性上同时拥有一个 setter 器。

使用 set 语法时请注意:

  • 它的标识符可以是数字或字符串;
  • 它必须有一个明确的参数(详见 Incompatible ES5 change: literal getter and setter functions must now have exactly zero or one arguments);
  • 在对象字面量中,不能为一个已有真实值的变量使用 set,也不能为一个属性设置多个 set。 ( { set x(v) { }, set x(v) { } } 和 { x: …, set x(v) { } } 是不允许的 )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值