【JS基础】理解JS中的面向对象

153 阅读8分钟

image.png

对象的概念

计算机科学中,对象(英语:object,台湾译作物件)是一个存储器地址,其中拥有,这个地址可能有标识符指向此处。对象可以是一个变量,一个数据结构,或是一个函数。是面向对象(Object Oriented)中的术语,既表示客观世界问题空间(Namespace)中的某个具体的事物,又表示软件系统解空间中的基本元素。

在软件系统中,对象具有唯一的标识符,对象包括属性(Properties)和方法(Methods),属性就是需要记忆的信息,方法就是对象能够提供的服务。在面向对象(Object Oriented)的软件中,对象(Object)是某一个(Class)的实例(Instance)。 《维基百科》

实际上,我们站在人类的角度上来看,对象其实就是一类事物的一个具体实例或者统一描述,它一定有它的属性或者活动,举个例子,幼时我们第一次喝到牛奶,以后便意识到所有牛奶都是可以喝的,从而衍生到纯牛奶可以喝、调制乳可以喝、酸奶可以喝,这里的牛奶就是一个类。计算机语言只不过是通过语言特性对这样一个东西进行一个描述。

这里我们以C++为例看一下其它计算机语言是怎样描述一个类的:

class test{
    public: //公有属性
        int prop_1;
        string prop_2;
        bool prop_3;
        
        int get_prop1();//成员函数
        test(); //构造函数
        ~test(); //析构函数 
        
   private: //私有属性
       int prop_4;
       string prop_5;
  }

在Grandy Booch《面向对象分析与设计》)。总结来看,对象有如下几个特点。对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。对象有状态:对象具有状态,同一对象可能处于不同状态之下。对象具有行为:

  1. 对象具有唯一标识性:即使看起来完全相同的两个对象,也并非同一个对象。
const obj1={a:1};
const obj2={a:1};
console.log(obj1==obj2) //false
  1. 对象有状态:对象具有状态,同一对象可能处于不同状态之下。(C++中的成员变量、JS的属性)
  2. 对象有改变状态的行为: 对象的属性可能由于它的函数而产生变化。(C++的成员函数、JS的属性)

通常来讲,原型是描述对象的两种不同方式,这里C++就采用了前者,而JS采用了后者,而由于JS当初创造时的一些形势政策原因,JS创建者被要求模仿JAVA,因此多了new、this这些新的语言特性,但是由于面向对象是为了方便人类理解而引入的东西,计算机运行时是面向过程的,即使不同语言描述对象的方式不同,但是总的来说他们的对象模型设计基本上是殊途同归的。

JS描述对象

在ECMA-262,对象被描述成无序属性的集合,也就是说,对象是一组键值对应得组合,键也就是属性名称,值也就是属性值,这一点和我们上面所描述的C++等强类型语言是不同的,其原因主要归结于JS语言的特点(弱化类型)。JS的对象属性可以分为两种,也就是数据属性和访问器属性。

数据属性

数据属性有四种,他们是属性的内部值,因此,将他们放到了两对方括号中:

  • [[Configurable]]表示属性是否可以删除而重新定义
  • [[Enumberable]]表示属性是否可以枚举,也就是For...in
  • [[Writable]]表示属性是否可以被修改,默认值为true
  • [[Value]] 表示属性的真实值 默认情况下,这些属性全为true 如果需要修改这些属性,则需要使用Object.defineProperty
  let person={
    name:"张三"
    }

Object.defineProperty(person,"name",{
   configurable:true,
   enumerable:false,
   writable:false,
   value:"李四"
})

 console.log(person)
 person.name="王五";
 console.log(person)

image.png

访问器属性

访问器属性同样四种:

  • [[Configurable]]表示属性是否可以删除而重新定义
  • [[Enumberable]]表示属性是否可以枚举,也就是For...in
  • [[Get]]表示读取属性时用到的函数,返回有效值,默认undefined
  • [[Set]] 表示写入属性时用到的函数,处理数据,默认undefined
let person={
    _name:"张三"
}

Object.defineProperty(person,"name",{
    get:function(){
        return this._name;
    },
    set:function (newname) {
        if(newname=="王五")
        this._name=newname;
    }
})

console.log(person.name)//张三
person.name="李四"
console.log(person.name)//张三
person.name="王五"
console.log(person.name)//王五

//一定程度上_name配合get set可以实现私有属性

对于内定的对象属性我们可以用Object.getOwnPropertyDescriptor(person,"_name")查看

image.png

基于类与基于原型

基于类编程(英语:class-based programming),又称基于类的编程面向类(class-orientation),是面向对象编程(OOP)的一种风格,在程序设计时,强调对象(object)的类别(class)。

在这种编程范型中,一个对象必须基于类别,才能被创造出来;此乃它跟重视对象本身的基于原型编程的差异。因此,用这种方式被创造出来的对象,被视为是类的实例(instance)。因为所有创建的对象都是类的实例,实例间唯一允许的差异性只有状态,因此用这种方式创建的程序,稳定性较高,安全性也较高。但由于类别的限制,实例除了状态外不允许有其它差异性,因此在类别发布之后,要进行修改,更新类别的结构与行为就不是一件容易的事,引用旧有类别的程序,都会受到影响,需要同步修改,因此这种程序设计风格需要事前较细致的规划。 《维基百科》

基于原型编程(英语:prototype-based programming)或称为原型程序设计原型编程,是面向对象编程的一种风格和方式。在原型编程中,行为重用(在基于类的语言通常称为继承),是通过复制已经存在的原型对象的过程实现的。这个模型一般被认为是无类的、面向原型、或者是基于实例的编程。

原型编程最初的(也是最经典的)例子是编程语言Self,它是由David Ungar和Randall Smith开发的。但是无类编程方式最近变得越来越受欢迎,并且被JavaScriptCecilNewtonScriptIoREBOL,还有一些其他的程序语言所采纳。 《维基百科》

前文我们提到,JS实现对象的思想和传统强类型语言不太一样,JS的一个对象实际上是一系列属性的无序集合,并且在这种面向对象方式的基础上提高了对象的动态性,比如运行时添加属性:

const people={
    name:"张三"
}
people.name="李四"
console.log(people)

image.png

回过头来看,传统强类型语言实现对象实际上是由于类型要求严格,各个对象之间严格分类,想要创造一个对象,那么必须先去创造这个对象的一个模板或者说类型,相较之下,基于原型使得开发者不用去考虑如何创造一个模板或者是如何去模仿父类,使得万物基于Object,同时也使得开发者更加注重到前文我们提到的改变对象的行为

实际上,基于原型的程序设计模式有两种方式:

  • 并不真正的复制一个对象,而是实现这个对象的引用
  • 切实的复制一个对象 很明显,JS设计之初选择了前者

原型函数、构造函数、实例之间的关系

每一个函数创建的时候都会被赋予一个proptype属性。默认情况下,所有的原型对象都会增加一个consructor属性,指向函数,即构造函数。当通过new方法调用创建一个实例的时候,实例具有一个__proto__属性,指向构造函数的原型对象。

function Person(){}
Person.prototype.name="Tom"
let person1=new Person();
let person2=new Person();
console.log(person1.__proto__)//{name: 'Tom', constructor: ƒ}
console.log(Person.prototype)//{name: 'Tom', constructor: ƒ}

实例属性读取顺序


function Person(){
    this.name="张三"
}

Person.prototype.name="李四"
Person.prototype.age=18

let person=new Person();

console.log(person.name)//张三
console.log(person.age)//18,去原型链找
console.log(person.prototype)//undefined
console.log(person.__proto__.name)//李四

重写原型对象

const Person=function(name){
    this.name=name;
}
Person.prototype={
    constructor:Person, //重要
    name:"张三",
    age:18,
    sayHello:function(){
        console.log("lalala")
    }
}
let person=new Person("王麻子")
console.log(person.name)
delete person.name
console.log(person.name)
console.log(person.age)
person.sayHello()

如果不设置constructor则会切断与原型链的联系

const Person=function(name){
    this.name=name;
}
Person.prototype={
    // constructor:Person,
    name:"张三",
    age:18,
    sayHello:function(){
        console.log("lalala")
    }
}
let person=new Person("王麻子")
console.log(person.name)
delete person.name
console.log(person.name)
console.log(person.age)
person.sayHello()

person.__proto__.constructor===Person//false
person.__proto__.constructor===Object//true

The End

image.png