Js面向对象-Class

218 阅读7分钟

对象的定义

在ECMAScript-262中,对象被定义为**“无序属性的集合,其属性可以包含基本值,对象或者函数”**。

也就是说,在JavaScript中,对象无非就是由一些列无序的key-value对组成。其中value可以是基本值,对象或者函数。

对象的原型

参考   Js高级---原型和原型链

对象的创建

1、简单对象的创建

通过new操作符,new的过程参考 Js高级---new

var obj = new Object();

通过对象字面量

var obj = {};

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

Object.create(proto[, propertiesObject])

2、复杂对象的创建

参考  Js高级---创建对象的多种方式

对象的属性

在ECMAScript5中,对每个属性都添加了几个属性类型,来描述这些属性的特点。他们分别是:

  • configurable: 表示该属性是否能被delete删除。当其值为false时,其他的特性也不能被改变。默认值为true
  • enumerable: 是否能枚举。也就是是否能被for-in遍历。默认值为true
  • writable: 是否能修改值。默认为true
  • value: 该属性的具体值是多少。默认为undefined
  • get: 当我们通过person.name访问name的值时,get将被调用。该方法可以自定义返回的具体值是多少。get默认值为undefined
  • set: 当我们通过person.name = 'Jake'设置name的值时,set方法将被调用。该方法可以自定义设置值的具体方式。set默认值为undefined

需要注意的是,不能同时设置value、writable 与 get、set的值。

我们可以通过Object.defineProperty方法来修改这些属性类型。

下面我们用一些简单的例子来演示一下这些属性类型的具体表现。

configurable

// 用普通的方式给person对象添加一个name属性,值为TOM
var person = {
    name: 'TOM'
}

// 使用delete删除该属性
delete person.name;  // 返回true 表示删除成功

// 通过Object.defineProperty重新添加name属性
// 并设置name的属性类型的configurable为false,表示不能再用delete删除
Object.defineProperty(person, 'name', {
    configurable: false,
    value: 'Jake'  // 设置name属性的值
})

// 再次delete,已经不能删除了
delete person.name   // false
console.log(person.name)    // 值为Jake
// 试图改变value
person.name = "alex";
console.log(person.name) // Jake 改变失败

enumerable

var person = {
    name: 'TOM',
    age: 20
}
// 使用for-in枚举person的属性
var params = [];
for(var key in person) {
    params.push(key);
}
// 查看枚举结果
console.log(params);  // ['name', 'age']
// 重新设置name属性的类型,让其不可被枚举
Object.defineProperty(person, 'name', {
    enumerable: false
})
var params_ = [];
for(var key in person) {
    params_.push(key)
}
// 再次查看枚举结果
console.log(params_); // ['age']

writable

var person = {
    name: 'TOM'
}
// 修改name的值
person.name = 'Jake';
// 查看修改结果
console.log(person.name); // Jake 修改成功
// 设置name的值不能被修改
Object.defineProperty(person, 'name', {
    writable: false
})
// 再次试图修改name的值
person.name = 'alex';
console.log(person.name); // Jake 修改失败

value

var person = {}

// 添加一个name属性
Object.defineProperty(person, 'name', {
    value: 'TOM'
})

console.log(person.name)  // TOM

get/set

var person = {}

// 通过get与set自定义访问与设置name属性的方式
Object.defineProperty(person, 'name', {
    get: function() {
        // 一直返回TOM
        return 'TOM'
    },
    set: function(value) {
        // 设置name属性时,返回该字符串,value为新值
        console.log(value + ' in set');
    }
})

// 第一次访问name,调用get
console.log(person.name)   // TOM

// 尝试修改name值,此时set方法被调用
person.name = 'alex'   // alex in set

// 第二次访问name,还是调用get
console.log(person.name) // TOM

请尽量同时设置get、set。如果仅仅只设置了get,那么我们将无法设置该属性值。如果仅仅只设置了set,我们也无法读取该属性的值。

Object.defineProperty只能设置一个属性的属性特性。当我们想要同时设置多个属性的特性时,需要使用我们之前提到过的Object.defineProperties

var person = {}
Object.defineProperties(person, {
    name: {
        value: 'Jake',
        configurable: true
    },
    age: {
        get: function() {
            return this.value || 22
        },
        set: function(value) {
            this.value = value
        }
    }
})
person.name   // Jake
person.age    // 22

读取属性的特性值

我们可以使用Object.getOwnPropertyDescriptor方法读取某一个属性的特性值。

var person = {}
Object.defineProperty(person, 'name', {
    value: 'alex',
    writable: false,
    configurable: false
})
var descripter = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descripter);  // 返回结果如下
descripter = {
    configurable: false,
    enumerable: false,
    value: 'alex',
    writable: false
}

对象的方法

1.内置方法

Object.is()  , Object.assign()  等

参考:es6对象的新增方法

2.实例方法

function Person(name,age){
    // 实例属性 
    this.name = name
    this.age = age
    // 实例方法
    this.eat = function(){
        console.log(this.name + '吃饭')
    }
}

// 通过构造函数创建出实例p
let p = new Person("123",28)
// 通过实例p去访问实例属性
console.log(p.name) 
// 通过实例p去访问实例方法
p.eat() 

3.静态方法 

function foo(){
    this.show = function(){
        return this;
    }
}

foo.test = 123; //静态属性

foo.say = function(){//静态方法
    return this;
}
foo.say();

var fn = new foo(); //实例化的新的对象,this指向这个新的对象,不能访问类的静态方法
fn.say(); //Noname1.html:45 Uncaught TypeError: fn.say is not a function
console.log(foo.say() == fn.say());

4.原型方法

function Person(name,age){
    this.name = name
    this.age = age
}

Person.prototype.eat = function(){ // 使用prototype找到该Person的原型对象
    console.log(this.name + '吃饭')
}

let p1 = new Person("123",28)
let p2 = new Person("swr",28)
console.log(p1.eat === p2.eat) // true
p1.eat() 

对象的访问

假如我们有一个简单的对象如下:

var person = {
    name: 'TOM',
    age: '20',
    getName: function() {
        return this.name
    }
}

当我们想要访问他的name属性时,可以用如下两种方式访问。

person.name

// 或者
person['name']

如果想要访问的属性名是一个变量时,常常会使用第二种方式。例如我们要同时访问person的name与age,可以这样写:

['name', 'age'].forEach(function(item) {
    console.log(person[item]);
})

这种方式一定要重视,记住它以后在我们处理复杂数据的时候会有很大的帮助。

对象的拷贝

浅拷贝和深拷贝

参考   Js高级---浅拷贝与深拷贝

封装

封装的定义:

  1. 把对象的状态和行为看成一个统一的整体,将二者抽象在一个独立的模块中,比如:类;
  2. 细节隐藏, 把不想对外公开的实现细节隐藏起来,保证调用者安全访问,不会出现因为越界导致本不应该出现的问题出现;

封装的好处:

  1. 调用者能够正确、方便地使用系统功能,有效地防止调用者随意修改系统属性,提高安全性
  2. 把特定的功能封装起来,提高功能的重用性
  3. 降低功能组件之间的耦合性,即使一个模块的实现细节发生改变,只要保证对外暴露的接口或者方法保持不变,就不会影响到其他调用者。

封装的方式:

参考 Js高级---创建对象的多种方式

继承

继承的定义:

从面向对象的角度上说,继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是对父类的拓展,是一种特殊的父类,所以可以基于父类并对其加以拓展,产生新的子类定义,这就是继承。

继承的好处:

子类可以通过继承获得父类原有的字段和方法,也可以增加父类所没有的字段和方法,更是可以覆写父类中的允许被子类覆盖的字段或者方法。

继承的方式:

参考  Js高级---继承

多态

多态的定义:

允许将子类类型的指针赋值给父类类型的指针。

多态的表现:

方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。

方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。

参考文章

js:面向对象编程,带你认识封装、继承和多态

前端基础进阶(十一):详解面向对象、构造函数、原型与原型链

javascript中对象字面量的理解

你不知道的JavaScript对象

2020年了,你真的了解js对象的属性吗

前端升级打怪路:JS对象(十三)

轻松理解JS中的面向对象,顺便搞懂prototype和__proto__

前端面试的那些事儿 ~ JavaScript 对象(面向对象、继承、对象拷贝)

小邵教你玩转JS面向对象