对象基础认知


  1. JS数据类型划分
  • 简单类型(基本类型):数字、字符串、布尔值、 null 、 undefined
    特点:看似像对象、拥有方法,但本身不可变
  • 引用类型(全都是对象):数组、函数、正则、普通对象等
    特点:可变的键控集合(key-value),可自由增删属性

  1. 对象核心特性
  • 对象是属性的容器,每个属性由属性名 + 属性值组成
  • 属性名可以是任意字符串;属性值可以是任意类型(除 undefined )
  • JS是无类(class-free)语言,无需提前定义结构,可动态新增属性
  • 对象支持嵌套,天然适合表示树形、图形类复杂数据结构
  • 原型链:JS特有继承机制,减少初始化时间与内存消耗

对象字面量

  1. 定义
    对象字面量是用一对大括号 {} 创建对象的便捷语法,可直接定义多个 名/值 对

  1. 语法规则

(1)属性名规则

  • 合法JS标识符可省略引号(如 first_name: “xxx” )​
  • 含 - 、空格等特殊字符必须加引号(如 “first-name”: “xxx” )

(2) 属性值:可以是任意表达式,支持对象嵌套
示例:

var flight = {
  airline: "Oceanic",
  number: 815,
  departure: { IATA: "SYD", time: "2004-09-22 14:55" }//嵌套
};

检索

(1)两种取值语法

  1. 点语法 . :仅支持合法标识符可读性更强(优先使用)
flight.departure.IATA
  1. 中括号语法 [ ] :支持任意字符串属性名
stooge["first-name"]

(2)取值规则
1.访问不存在的属性,返回 undefined


2.短路运算符的两个经典用法

  • || 给属性设置默认值
let status = flight.status || "unknown";

上面的语句可以理解为status 要么是 flight.status,要么是 “unknow”
因为 || 本身就代表 或者的意思

  • && 规避 undefined 取值报错(防止 TypeError )
    如果你检索到了一个undefined 的值就会抛出TypeError异常,而 && 就能避免错误
    这就类似于Java访问到了一个空链表的next,此时的链表已经是空,再访问null的next,编译系统完全无法识别应该去往哪里,就会抛出异常
flight.equipment//它的取值类型是 undefined
flight.equipment.model// throw “TypeError”
flight.equipment && flight.equipment.model

为什么 && 能避免报错呢?
本质上是让编译系统先判断 flight.equipment.model 的上一层 flight.equipment 是否为空,如果上一层已经为null了,那就不需要再执行下一个判断,继续运行下一条语句了

更新

(1)赋值规则

  1. 属性已存在:赋值直接覆盖原有值

  2. 属性不存在:赋值会动态新增该属性(JS对象可动态扩展)

(2) 示例

// 覆盖已有属性
stooge['first-name'] = 'Jerome';
// 动态新增属性
stooge.nickname = 'Curly';

引用

(1)核心规则
对象通过引用传递,永远不会被拷贝,多个变量指向同一个对象时,修改任意一个变量,所有变量都会同步变化

(2) 两种场景示例

  • 多个变量引用同一个对象
let x = stooge;
x.nickname = "Curly";
let nick = stooge.nickname
// x.nickname 和 nick 都指向 “Curly” 
  • 链式赋值:所有变量指向同一个空对象
var a = {}, b = {}, c = {}; // 三个独立空对象
a = b = c = {}; // a、b、c 都指向同一个空对象,此时a改变了 b也会改变

原型 prototype

(1)原型基础概念

  • 所有字面量创建的对象,原型默认指向 Object.prototype
  • 原型的核心作用:实现属性委托继承,子类对象可复用原型的属性,节省内存

(2) 手写原型继承: Object.beget (底层原理)

if (typeof Object.beget !== 'function') {
  Object.beget = function (o) {
    var F = function () {}; // 空构造函数,充当“桥梁”
    F.prototype = o; // 将构造函数原型指向传入的原型对象
    return new F(); // 返回新实例,实例原型自动绑定 o
  };
}

(3)原型核心特性

  1. 更新时:原型连接不生效写屏蔽读继承
    给子对象新增同名属性,只会修改自身,完全不影响原型对象
var another_stooge = Object.beget(stooge);
another_stooge["first-name"] = "Harry"; // 仅修改自身,stooge 完全不变
  1. 检索时:原型链向上委托
    读取属性时,先查自身,没有就顺着原型链向上找,直到 Object.prototype ;全程找不到则返回 undefined
  2. 原型动态性:给原型新增属性,所有基于该原型创建的对象会立刻继承该属性
stooge.profession = 'actor';
another_stooge.profession; // 自动读取原型属性,结果为 'actor'

反射

(1) 定义
反射:动态检测对象拥有哪些属性,核心工具为 typeofhasOwnProperty


(2) typeof 运算符

作用:检测属性值的类型,但会把原型链上继承的方法(如 toString 、 constructor )一并识别为 function


(3) hasOwnProperty

  • 作用:判断属性是否为对象自身独有,完全不检查原型链

  • 返回值: true = 自身属性; false = 继承属性 / 不存在​

  • 示例

flight.hasOwnProperty('number'); // true(自身属性)
flight.hasOwnProperty('constructor'); // false(原型继承属性)

枚举 for

(1) for…in 遍历对象

  • 作用:遍历对象所有属性名(key)
  • 致命缺陷:会遍历原型链上继承的属性、方法,属性遍历顺序不固定
  • 标准写法:必须搭配 hasOwnProperty 过滤原型属性
for (name in another_stooge) {
  // 只遍历自身属性,排除原型继承的方法
  if (typeof another_stooge[name] !== 'function') {
    document.writeln(name + ': ' + another_stooge[name]);
  }
}

(2) 替代方案(推荐)

手动定义属性数组 + 普通 for 循环,顺序可控、不触碰原型链,更稳定可靠。
即:for(let i=0 ; i<nums.length ; i++)


这里再补充一个for…of
for…of (遍历值,专门给数组/可迭代对象用
作用:ES6 新增,专门遍历数组、Map、Set、字符串等可迭代对象,拿值(value)

特点:

  • 遍历值本身,不是下标
  • 完全不碰原型链,安全干净
  • 可以配合 break 、 continue
  • 不能直接遍历普通对象(普通对象不是可迭代对象)

示例

const arr = [10, 20, 30];
for (let value of arr) {
  console.log(value); // 输出:10 20 30
}

删除 delete

(1) delete 运算符规则

  • 仅删除对象自身属性,完全不触碰原型链
  • 删除后,若原型存在同名属性,会自动浮现原型的属性值

(2) 示例

delete another_stooge.nickname;
// 删除自身属性后,会读取原型 stooge 的 nickname
another_stooge.nickname;

减少全局变量污染

(1) 全局变量的弊端

全局变量过多,易出现命名冲突、代码耦合度高、灵活性变差


(2) 解决方案:单一全局变量(命名空间)

  1. 只创建一个顶层全局对象,所有变量、对象都挂载到这个对象上
  2. 示例
let MYAPP = {}; // 唯一全局变量
MYAPP.stooge = { "first-name": "Joe" };
MYAPP.flight = { airline: "Oceanic" };

(3)优势
大幅降低命名冲突概率,代码结构清晰,可读性更强。

更多推荐