JavaScript 对象

对象

概述

对象(object)是JavaScript的核心概念,也是最重要的数据类型。JavaScript的所有数据都可以被视为对象。

JavaScript是一门面向对象的语言,但JavaScript没有类的概念,一切都是对象。任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建。引用类型可以是原生的,也可以是自定义的。

创建对象

对象字面量模式

var user = {
    name: "小明",
    age: 18,
    address: "beijing",
    sayHello: function() {
        console.log("hello");
    }
}

console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

工厂模式

工厂模式是软件工程领域中一种广为人知的设计模式,工厂模式抽象了创建具体对象的过程,用函数来封装以特定的接口创建对象的细节。

function createUser(name, age, address) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.address = address;
    obj.sayHello = function() {
        console.log("hello");
    }
    return obj;
}

var user = createUser("小明", 18, "beijing");
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

构造函数模式

所谓“构造函数”,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

function User(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.sayHello = function() {
        console.log("hello");
    }
}

var user = new User("小明", 18, "beijing");
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

原型模式

JavaScript中创建的每个函数都有一个prototype(原型)属性,它是一个指针,指向一个称为“原型对象”的对象,原型包含了所有实例共享的属性和方法。对所有实例共享的属性和方法,可以将其放到原型对象中,这样对这些共享属性和方法只需定义一次就可以了。

function User() {}
User.prototype.name = "小明";
User.prototype.age = 18;
User.prototype.address = "beijing";
User.prototype.sayHello = function() {
    console.log("hello");
};

var user = new User();
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

组合使用(构造函数模式和原型模式)

组合使用构造函数模式和原型模式中,构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。这样每个实例都会有自己的一份实例属性的副本,同时也可以共享对方法的引用,最大限度地节省了内存。

function User(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;

}
User.prototype = {
    constructor: User,
    sayHello: function() {
        console.log("hello");
    }
}


var user = new User("小明", 18, "beijing");
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

动态原型模式

动态原型模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行if语句内部的原型初始化代码。

function User(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
    if (typeof this.sayHello != "function") {
        User.prototype.sayHello = function() {
            console.log("hello");
        }
    }
}

var user = new User("小明", 18, "beijing");
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

使用

访问对象属性和方法

方式一

对象.属性名
对象.方法名()

方式二

对象["属性名"]
对象["方法名"]

使用

var user = {
    name: "小明",
    age: 18,
    address: "beijing",
    sayHello: function() {
        console.log("hello");
    }
}

//方式一
console.log(user.name);
console.log(user.age);
console.log(user.address);
user.sayHello();

//方式二
console.log(user["name"]);
console.log(user["age"]);
console.log(user["address"]);
user["sayHello"]();

删除属性

var user = {
    name: "小明",
    age: 18,
    address: "beijing",
    sayHello: function() {
        console.log("hello");
    }
}
delete user.name //删除属性
console.log(user); //{age: 18, address: "beijing", sayHello: ƒ}

遍历对象

const user = {
    name: "Tom",
    age: 18,
    sex: "man"
};

for-in

for (let k in user) {
    console.log(`键:${k} 值:${user[k]}`);
}

//键:name 值:Tom
//键:age 值:18
//键:sex 值:man

for-of

for (let [key, value] of Object.entries(user)) {
    console.log(`键:${key} 值:${value}`);
}

//键:name 值:Tom
//键:age 值:18
//键:sex 值:man

kes values entries

const keyArr = Object.keys(user);
console.log(keyArr); //["name", "age", "sex"]

const valueArr = Object.values(user);
console.log(valueArr); //["Tom", 18, "man"]

const entries = Object.entries(user);
console.log(entries); //[Array(2), Array(2), Array(2)]

hasOwnProperty

hasOwnProperty()方法用于判断属性是否为自身拥有的,非原型。

function User(name, age) {
    this.name = name;
    this.age = age;
}
User.prototype.address = "beijing";
var user = new User("xiaoming", 18);
console.log(user.hasOwnProperty("name")); //true
console.log(user.hasOwnProperty("age")); //true
console.log(user.hasOwnProperty("address")); //false
console.log(user.hasOwnProperty("toString")); //false

in

in操作法可以检查属性和方法是否可以被对象访问。

function User(name, age) {
    this.name = name;
    this.age = age;
}
User.prototype.address = "beijing";
var user = new User("xiaoming", 18);
console.log("name" in user); //true
console.log("age" in user); //true
console.log("address" in user); //true
console.log("toString" in user); //true

toString() 和 valueOf()

toString()和valueOf()是Object对象提供的两个方法,toString()用于返回对象的字符串表示,valueOf()则用于返回对象的原始值。所有对象都是继承了Object对象,因而所有对象都具有toString()和valueOf()这两个方法。

Object对象的toString()方法返回值是[object Object],而valueOf()方法返回的是对象本身。

function User(name, age) {
    this.name = name;
    this.age = age;
    this.toString = function() {
        return "姓名:" + name + " 年龄:" + age;
    }
    this.valueOf = function() {
        return "name:" + name + " age:" + age;
    }
}

var user = new User("小明", 18);
console.log(user.toString()); //姓名:小明 年龄:18
console.log(user.valueOf()); //name:小明 age:18

Object的静态函数

Object.create()

创建并返回一个指定原型和指定属性的对象。

语法

Object.create(prototype, propertyDescriptor)。

说明

参数prototype:对象的原型,可以为null。当为null时,对象的原型为undefined。

参数propertyDescriptor格式如下:

属性名: {
    value: "", // 设置属性的值。
    writable: true, // 属性是否是否能修改,默认值为true。
    enumerable: true, // 属性是否为可枚举,默认值为true,可枚举的属性表示能for..in操作。
    configurable: true //属性是否删除而重新定义,默认值为true。
}

使用

var obj = Object.create(null, {
    name: {
        value: "小明",
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 18
    }
});
console.log(obj.__proto__); //undefined
console.log(obj.name); //小明
console.log(obj.age); //18

obj.age = 28;
console.log(obj.age); //18。writable为false,表示只读。

for (var i in obj) {
    console.log(i);
}
//name。只输出name属性;age属性enumerable为false,不能被for...in。

Object.defineProperty()

添加或修改对象的属性值。

语法

Object.defineProperty(目标对象, "属性明", 属性描述)

使用

Object.defineProperty(user, "name", {
    value: "小明",
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(user); //{name: "小明"}

Object.defineProperties()

添加或修改对象的属性值。

语法

Object.defineProperties(目标对象, 属性描述)

使用

//给一个空对象{}添加name和age属性
var obj = {};
Object.defineProperties(obj, {
    name: {
        value: "小明",
        enumerable: true
    },
    age: {
        value: 18,
        enumerable: true
    }
});

for (var i in obj) {
    console.log(i);
}
// name
// age

Object.getOwnPropertyNames()

获取对象的所有实例属性和函数,不包含原型链继承的属性和函数,数据格式为数组。

function Person(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.getName = function() {
        return this.name;
    }
}
Person.prototype.sayHello = function() {
    console.log("hello");
}

var p = new Person();
console.log(Object.getOwnPropertyNames(p)); //["name", "age", "address", "getName"]

Object.keys()

获取对象可枚举的实例属性,不包含原型链继承的属性,数据格式为数组。

var obj = {
    name: "小明",
    age: 18,
    sayHello: function() {
        console.log("hello");
    }
}
Object.defineProperties(obj, {
    name: {
        enumerable: false
    }
});
console.log(Object.keys(obj)); //["age", "sayHello"]

判断空对象

使用JSON.stringify

function isEmptyObject(obj) {
    return JSON.stringify(obj) === "{}";
}

var obj = {};
var obj2 = {
    name: "小明"
};

console.log(isEmptyObject(obj)); //true
console.log(isEmptyObject(obj2)); //false

使用Object.keys

function isEmptyObject(obj) {
    return Object.keys(obj).length === 0;
}

原型和原型链

new的流程

var person = new Person();

等价于:

var person = {};
person.__proto__ = Person.prototype;
Person.call(person);
  1. 创建一个空对象。
  2. 空对象的__proto__隐式属性指向Person对象的prototype属性。
  3. 将Person()函数的this指向person对象。

原型对象、构造函数、实例

构造函数在创建时都拥有一个prototype属性,prototype属性也就是指向函数的原型对象。在默认情况下,所有的原型对象都会增加一个constructor属性, 并指向prototype属性所在的函数,即构造函数。

通过new调用构造函数创建的实例,实例拥有一个__proto__属性,指向构造函数的原型对象,因此__proto__属性可以看作是一个连接实例与构造函数的原型对象的桥梁。

场景一

function Person(name, age) {
    this.name = name;
    this.age = age;
}
var person1 = new Person("小明", 18);
var person2 = new Person("小花", 28);
console.log(Person.prototype === person1.__proto__); //true
console.log(Person.prototype === person2.__proto__); //true
console.log(Person.prototype.constructor === Person); //true

说明:构造函数Person的prototype属性指向Person的原型对象,person1实例和person2实例的__proto__隐式属性指向Person的原型对象。

在这里插入图片描述

场景二

function Foo() {}
console.log(typeof Object); //function
console.log(Object.__proto__ === Function.prototype); //true
console.log(Object.__proto__.__proto__ === Function.prototype.__proto__); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
console.log(Object.__proto__.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); //null

说明:Object是一个function类型,因此Object的__proto__隐式属性指向Function的原型对象。

在这里插入图片描述

属性访问顺序

当我们通过对象的实例读取某个属性时,是有一个搜索过程的。它会先在实例本身去找指定的属性,如果找到了,则直接返回该属性的值;如果没找到,则会继续沿着原型对象寻找;如果在原型对象中找到了该属性,则返回该属性的值。

function Person() {
    this.name = "小明";
}
Person.prototype.name = "小花";
var p = new Person();
console.log(p.name); //小明

原型链

对象的每个实例都具有一个__proto__属性,指向的是构造函数的原型对象,而原型对象同样存在一个__proto__属性指向上一级构造函数的原型对象,就这样层层往上,直到最上层某个原型对象为null

在JavaScript中几乎所有的对象都具有__proto__属性,由__proto__属性连接而成的链路构成了JavaScript的原型链,原型链的顶端是Object.prototype,它的__proto__属性为null

function Person() {}
var p = new Person();
console.log(p.__proto__ === Person.prototype); //true
console.log(p.__proto__.__proto__ === Person.prototype.__proto__); //true
console.log(p.__proto__.__proto__ === Object.prototype); //true
console.log(p.__proto__.__proto__.__proto__ === null); //true

四大特性

同C++、Java等大多数面向对象语言一样,JavaScript存在抽象、封装、继承和多态四大特性。

抽象性

是创建一个“类”,用来描述具有相似性的一类事物。即使需求变了,通性也不会变,这就是抽象。

封装性

封装就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(成员方法),才能对数据进行操作。

继承性

继承也可以理解为扩展性。

在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass。

由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型。

继承实现的本质是重写原型对象,用父对象的实例替换子对象的默认原型对象。

原型链继承
function Person() {
    this.country = "China";
}
Person.prototype.printCountry = function() {
    console.log("来自于" + this.country);
}

function Student(name, age) {
    this.name = name;
    this.age = age;
}
Student.prototype = new Person();
Student.prototype.printInfo = function() {
    console.log("姓名:" + this.name + " 年龄:" + this.age);
}

var stu = new Student("小明", 18);
stu.printInfo(); //姓名:小明 年龄:18
stu.printCountry(); //来自于China
构造继承
function Animal(age) {
    this.name = "animal";
    this.age = age;
    this.sleep = function() {
        return this.name + "在睡觉";
    }
}
Animal.prototype.eat = function(food) {
    return this.name + "吃" + food;
}

function Cat(name) {
    Animal.call(this);
    this.name = name || "Tom";
}

var cat = new Cat();
console.log(cat.sleep()); //Tom在睡觉
console.log(cat.eat()); //Uncaught TypeError,不能访问父类的原型方法
复制继承
function Animal(age) {
    this.name = "animal";
    this.age = age;
    this.sleep = function() {
        return this.name + "在睡觉";
    }
}
Animal.prototype.eat = function(food) {
    return this.name + "吃" + food;
}

function Cat(name, age) {
    var animal = new Animal(age);
    for (var key in animal) {
        if (animal.hasOwnProperty(key)) {
            this[key] = animal[key];
        } else {
            Cat.prototype[key] = animal[key];
        }
    }
    this.name = name || "Tom";
}

var cat = new Cat("小白", 18);
console.log(cat.sleep()); //小白在睡觉
console.log(cat.eat("沙丁鱼")); //小白吃沙丁鱼
组合继承

组合继承的主要思想是组合了构造继承和原型继承两种方法,一方面在子类的构造函数中通过call()函数调用父类的构造函数,将父类的实例的属性和函数绑定到子类的this中;另一方面,通过改变子类的prototype属性,继承父类的原型对象上的属性和函数。

function Animal() {
    this.name = "animal";
    this.sleep = function() {
        return this.name + "在睡觉";
    }
}
Animal.prototype.eat = function(food) {
    return this.name + "吃" + food;
}

function Cat(name) {
    Animal.call(this);
    this.name = name || "Tom";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

var cat = new Cat("小白");
console.log(cat.sleep()); //小白在睡觉
console.log(cat.eat("沙丁鱼")); //小白吃沙丁鱼

多态性

多态性是指相同操作方式带来不同的结果。

从形式上来说,有两种多态。分别为重载和重写:

  • 重写是指子类对父类的行为做了修改,虽然行为相同,但结果不同。例如改进性能的同系列车就比原先的某些方面的性能更优越。
  • 重载是指某一对象的相同行为导致了不同的结果。

多态一方面加强了程序的灵活性和适应性,另一方面也可以减少编码的工作量。

JavaScript实现重载的方式与Java等其他面向对象语言不同。JavaScript需要利用arguments属性来实现方法重载。

function User() {
    this.test = function() {
        if (arguments.length == 1) {
            this.show1(arguments[0]);
        } else if (arguments.length == 2) {
            this.show2(arguments[0], arguments[1]);
        } else if (arguments.length == 3) {
            this.show3(arguments[0], arguments[1], arguments[2]);
        }
    }
    this.show1 = function(a) {
        console.log("show1() 调用了");
    }
    this.show2 = function(a, b) {
        console.log("show2() 调用了");
    }
    this.show3 = function(a, b, c) {
        console.log("show3() 调用了");
    }
}

var user = new User();
user.test(1);
user.test(1, 2);
user.test(1, 2, 3);

包装类

  • Number() String() Boolean()分别是数值、字符串、布尔类型的包装类
  • 包装类的目的是为了让基本类型从它们的构造函数的prototype上获取方法
var a = new Number(100);
var b = new String("hello");
var c = new Boolean(true);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值