吃透JavaScript核心——对象

224 阅读6分钟

1 对象

1.1 为什么需要对象 什么是对象

1.1.1 为什么需要对象

对象表达结构更清晰、更强大。 保存一个值,用变量;保存多个值,用数组;保存一个人的完整信息,用对象

1.1.2 对象

对象是一个具体的事物,不是泛指的。JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象。例如:字符串、数值、数组、函数等。 对象是由属性方法组成的:

  • 属性:事物的特征,在对象中用属性来表示(常用名词)。
  • 方法:事物的行为,在对象中用方法来表示(常用动词)。

对象是属性(property)的集合, 属性包含一个名(key or name)和一个值(value)

// 对象字面值;key 需要是字符串
let folder1 = {
    'size': 2000,
    'name': 'folder1',
    'subFiles': ['index.js'],
    'other object': null
};
// console.log(typeof folder1, folder1);

// 如果是合法的标识符,可以省略引号:
let folder2 = {
    size: 2000,
    name: 'folder2',
    subFiles: ['index.js'],
    'other object': folder1
};

1.2 变量、属性、函数、方法

主要功能声明使用
变量存储数据单独声明并赋值直接写变量名
属性存储数据在对象里的属性不需要声明对象.属性
函数实现某种功能单独声明函数名()
方法实现某种功能在对象里的方法不需要声明对象.方法()
  • 对象的某个属性的值为函数,那么这个属性叫做对象的方法(函数)。

1.3 对象的属性的增删改查

  • 调用对象的属性
    • 对象名.属性名
    • 对象名['属性名']
  • 调用对象的方法
    • 对象名.方法名()

1.3.1 访问(查找)

// 访问对象属性: 1️⃣:.
console.log(folder2.size, folder2.name);

// 或者使用 2️⃣:[]
console.log(folder2['size'], folder2['name'], folder2['other object']);
// [] 里面可以填入任意的有效的值(表达式)
let sizeKey = 'size';
console.log(folder2[sizeKey], folder2['na' + 'me']);

// 3️⃣:链式调用
console.log(folder2["other object"].subFiles[0]);

// 如果访问一个不存在的属性,返回 undefined
console.log(folder2.data, folder2['any' + 'string']);

  • 注:对象的省略的写法
    // 一种更简要的写法
    const year = 2020;
    const month = 6;
    console.log({
        // 属性名是变量名,值是变量值,不用写 year: year。
        year,
        month,
    
        // 方法
        print() {
            console.log(year + '-' + month);
        }
    })
    

1.3.2 修改

  • 可以任意修改属性值
    folder2.size = folder2.size + 1000;
    folder2['name'] = null;
    folder2.subFiles = [];
    console.log(folder2.size, folder2.name, folder2.subFiles);
    
  • 对象的属性是可变的,可以增加更多的属性
    folder2.createTime = '2020-6-1';
    folder2['modifyTime'] = '2020-6-15';
    console.log(folder2);
    
  • 直接写对象字面值的时候,希望属性名是计算出来的,而不是固定的。应该先定义一个变量:
    const str = 'variable';
    const obj = {
        // 属性名是 variable
        [str]: 'computed key',
    
        // 数字会别转为字符串
        0.1: 'number as key',
    
        log: () => {
            console.log('func as value');
        }
    }
    console.log(obj.variable, obj[str]);
    
    // 数字会被转为字符串
    console.log(obj['0.1'], obj[0.1]);//obj.0.1 ❌
    
    obj.log();// 对象包含的函数也被称为方法
    

1.3.3 遍历对象属性

for...in 使用for in 里面的变量,常用k或者key写

const student = {
    name : '张三',
    age : 20,
    interests : ['跑步', '看书'],
    teacher: {
        name: '李四'
    }
};

// 1️⃣:Key方法 
//Object 是 JavaScript 提供的一个全局对象
console.log(Object.keys(student));//["name", "age", "interests", "teacher"]

//2️⃣:for in
for (const key in student) {
    console.log(key, student[key]);
}

1.3.4 判断对象是否包含某一属性

console.log(student.classmate === undefined); // ❌,可能本身存在一个值为 undefined 的属性

//1️⃣:遍历数组判断
Object.keys(student) 

//2️⃣:使用 in
console.log('classmate' in student, 'teacher' in student);

1.3.5 属性删除

// 属性删除,delete
delete student.teacher;
console.log('classmate' in student, 'teacher' in student);

2 对象的引用

2.1

  • 一个变量持有一个对象,持有的是这个对象的引用,或者说这个变量指向了这个对象。
    const emptyObj1 = {};
    const emptyObj2 = {};
    console.log(emptyObj1 !== emptyObj2);//ture
    
    const emptyObj3 = emptyObj1;//把一个对象赋值给一个变量,复制的是引用关系
    console.log(emptyObj1 === emptyObj3);//true
    
    emptyObj1.id = 'emptyObj1.id';
    console.log(emptyObj1.id, emptyObj2.id, emptyObj3.id);//emptyObj1.id undefined emptyObj1.id
    
  • const 定义对象,保证了引用的不可变性(即指向关系不可变),但对象的属性可变。而使用let引用会变。
    //emptyObj1 = emptyObj2; // ❌
    let emptyObj4 = emptyObj1;
    console.log(emptyObj4.id);//emptyObj1.id
    emptyObj4 = emptyObj2;
    console.log(emptyObj4.id);//undefined
    

2.2

  • 对于函数形参来说传入的是值,对于对象来说传入的这个值是对象的引用。
  • 在函数里直接改变对象的引用或者对象的变量的值,相当于只改变了函数参数的引用关系,外层的引用关系是不变的,外层的变量也不变。
    // 参数传入的是值;对于对象而言是,引用
    function changeParam(basicValue, object) {
        console.log('改变前', basicValue, object);
        basicValue = undefined;
        object = null;
        console.log('改变后', basicValue, object);
    }
    changeParam(1, emptyObj1);
    //改变前 1 > {id: "emptyObj1.id"}
    //改变后 undefined null
    
    console.log(emptyObj1);//{id: "emptyObj1.id"}
    

2.3

  • 引用关系不变,不代表对象的内容不可变。
    function addProperty(object) {
        const key = '__private__key__';
        object[key] = 'from addProperty func';
    }
    addProperty(emptyObj2);
    console.log(emptyObj2);//{__private__key__: "from addProperty func"}
    

3 创建对象的三种方式

3.1 利用对象字面量

  • {}
    var obj = {};
    var obj1 = {
        uname: 'niki',
        age: 18,//1. 键值对形式
        sex: '女',//2. 逗号隔开
        sayHi: function() { //3. 方法冒号后跟匿名函数
            console.log('hi~');
        }
    }
    console.log(obj.uname);
    

3.2 利用 new Object

  • 利用等号赋值的方法添加对象的属性和方法。
  • 每个属性和方法之间用;结束。
    var obj = new Object();
    obj.uname = 'niki';
    obj.age = 18;
    obj.sex = '女';
    obj.sayHi = function() { 
        console.log('hi~');
    }
    console.log(obj.uname);
    console.log(obj['sex']);
    obj.sayHi();
    

3.3 利用构造函数

✨✨✨

3.3.1 为什么需要用构造函数创建对象?

  • 🧷前两种方式一次只能创建一个对象。很多对象的很多属性和方法是大量相同的,只能一个又一个重复地创建
  • 🧷 利用函数的方法,重复这些相同的代码,就把这个函数称为构造函数
  • 🧷构造函数里面封装的不是普通代码,而是对象

3.3.2 什么是构造函数 语法格式

构造函数就是把我们对象里面一些相同的属性和方法抽象出来封装到函数里面。

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用。

function 构造函数名() {
    this.属性 = 值;
    this.方法 = function() {
    
    }
}
new 构造函数名();
function Star(uname,age,sex) {
    this.name = uname;
    this.age = age;
    this.sing = function(sang){
        console.log(sang);
    }
}
Star.sex= '男'//静态成员
var ldh = new Star('liudehua', 18, '男');
ldh.sing('冰雨')
var zxy = new Star('zhangxueyou',19,'男');//批量生产

注:

  • 构造函数名首字母要大写
  • 构造函数不需要return就可以返回结果。
  • 调用构造函数必须使用new
  • 只要new Star() 调用函数,就创建了一个对象。
  • 属性和方法前面必须添加this

3.3.3 构造函数和对象的联系&区别

构造函数是泛指的某一大类类似于java里的类(class)。

对象是特指某个具体的事物

通过new关键字利用构造函数创建对象的过程称为对象的实例化

3.3.4 new 关键字执行过程

  1. new 构造函数在内存中创建了一个空的对象。
  2. this 指向刚才创建的空对象。
  3. 执行构造函数里的代码,给这个空对象添加属性和方法。
  4. 返回这个对象。 (所以构造函数不需要return)

3.3.5 实例成员和静态成员

构造函数中的属性和方法称为成员,成员可以添加,成员分为两大类:

  • 实例成员:构造函数内部通过 this 添加的成员(uname、age),实例成员只能通过实例化的对象来访问。
  • 静态成员: 在构造函数本身上添加的成员(sex),静态成员只能通过构造函数访问。

4. 一个深拷贝案例

function clone(parent) {
    const allParents = [];
    const allChildren = [];

    function _clone(parent) {
        const child = {};
        if (parent === null) {
            return null;
        }
        if (typeof parent !== 'object') {
            return parent;
        }
        const index = allParents.indexOf(parent);
        if (index !== -1) {
            return allChildren[index];
        }
        //🚀避免出现循环引用导致的无限递归情况
        allParents.push(parent);
        allChildren.push(child);
        for (const key in parent) {
            const value = parent[key];
            child[key] = _clone(value);
        }
        return child;
    }
    return _clone(parent);
}
console.log(
    clone(undefined),
    clone(null),
    clone(1),
    clone(' '),
    clone({}),
    clone({id: 1, obj: {id: 2}}),
);

const circularObj = {};
circularObj.self = circularObj;
console.log(clone(circularObj));