Javascript 面向对象

一直在说面向对象,也在说 Javascript 是面向对象、面向过程、函数式编程的语言。那么到底什么是面向对象?javascript

面向对象程序设计(Object Oriented Programming,OOP):是一种计算机编程架构。OOP 的一条基本原则是计算机程序由单个可以起到子程序做用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性,其中核心概念是类和对象。java

了解了什么是面向对象,也知道了面向的核心概念是类和对象,那到底什么是类?什么是对象?类和对象的关系是什么?typescript

对象:人们研究所研究的事物自己,就是对象,例如一个具体的人、一棵树、一只狗、一条规则等等。对象包含自身属性,方法,实现数据和操做的结合。ps 学数据结构的时候,也看到过这句话,数据结构就是 数据 + 操做编程

:对相同的特性和行为的对象的抽象,例如 Person、Tree、Dog、Rule 等等,其中包含数据的形式和操做。类的实例就是对象浏览器

对象和类的关系:能够理解为 类是模具,对象是根据模具创造的产品babel

// 这是一个Person类
// 对人的抽象,定义了人的特性,例如名字,年龄,身高,体重等等的数据形式,也定义了人的行为,例如说话,跑步等等
class Person {
  name: string;
  age: number;
  height: number;
  weight: number;
  ...

  talk(){
    console.log('speak english');
  }
  run(){}
  ...
}

// keven 是一个对象,他是一个具体的人,他包含名字,年龄,身高...,也能够说话,跑步...
const keven:Person = new Person();
复制代码

面向对象三大特性

  1. 继承:子类继承父类,子类与父类表现得很像(继承了父类的特性和行为),固然子类也能够包含本身的特性和行为;数据结构

    // YellowPerson 继承了 Person,则继承了 Person 的特性和行为
    // 虽然 YellowPerson 内部未声明任何属性和方法,但它已经具备 name,age,height...
    class YellowPerson extends Person {
      playTableTennis() {}
    }
    复制代码
  2. 多态:子类重写父类,继承同一个父类的子类对同一个特性或行为会表现得不一样架构

    // 子类继承了父类,但子类对同一个特性 talk 表现的不一样,例如能够说中文,能够说非洲语
    class YellowPerson extends Person {
      talk() {
        console.log('说中文');
      }
    }
    
    class BlackPerson extends Person {
      talk() {
        console.log('说非洲语');
      }
    }
    复制代码
  3. 封装:内部实现细节对外部隐藏,使用属性描述符来控制成员的访问,属性描述符通常有:private、protected、public函数式编程

    class Person {
      private assets: number; // 他有不少资产,除了他本身,并不想让任何人知道
      protected houseKey: string; // 这我的不想让外人知道本身家的钥匙,除非是本身的家人,例如他的儿子
      public name: string; // 他的名字任何人均可以知道
    }
    复制代码

Javascript"面向对象"

上面说到 Javascript 是能够面向对象的,且面向对象的核心是类和对象,那么类和对象在 Javascript 是如歌表现的?函数

Javascript 的对象

Javascript 的数据类型分为:number,string,boolean,null,undefined,symbol 和 object,其中 object 就是咱们说的 Javascript 对象。因为存在其余的数据结构,因此 Javascript 并非全是对象,即在 Javascript 中,并不是万物皆是对象

Javascript 对象有不少,例若有如下内置对象 Object,Array,Function,RegExp...,固然你还能够本身建立对象,经常使用的有如下方式

const obj1 = {};
const obj2 = new Object(); // 构造调用,用的不多
const obj3 = new Person(); // 构造调用
const obj4 = Object.create(null);
复制代码

Javascript 对象属性、方法

属性和方法

你可使用 "." 或 "[]" 来访问属性、方法。咱们通常会对对象的成员加以区分:成员值为函数的称为方法,值为非函数称为属性,这是按照其余面向对象的语言来称呼的。

可是在 Javascript 中,一个函数其实不会属于某个对象(其实仅仅是一个引用),即该函数不会是某个对象的方法,因此对方法这个称呼不是十分严谨,它仅仅是在进行对象属性访问的时候,返回值是函数罢了《你不知道的 Javascript》。

有几个注意点

  1. "." 和 "[]" 区别:
    • "." 通常称为属性访问,且属性必须知足命名规范;"[]" 通常称为键访问,键名可接受任意的 utf-8/unicode 字符串
    • "." 属性名只能为常量;"[]" 能够为变量
    • "[]" 在 ES6 中可用于计算属性
    • "." 和 "[]" 在 AST 是不同的,. => PropertyAccessExpression; [] => ElementAccessExpression
[[GET]],[[PUT]]

对象的获取属性设置属性操做。这里注意一点,和 Getter、Setter 不同的是 [[GET]]、[[PUT]] 是针对对象的操做,Getter、Setter 是针对对象的某个属性的操做

由下面代码伪装模拟一个[[GET]] 和 [[PUT]],须要注意的是,[[GET]] 和 [[PUT]]并不是只关注本对象,还要按照原型链往上查找

const obj = {};

const proxy = new Proxy(obj, {
  get() {}, // 伪装当成[[GET]]
  set() {}, // 伪装当成[[PUT]]
});
复制代码
属性描述符

须要注意的是:属性描述符 configurable、enumerable、writable 默认都是 false

  1. 公共属性描述符: configurable、enumerable

    {
      configurable: false, // 该属性没法被改变,没法删除,没法从新设置属性描述,且对没法进行的操做静默失败。ps 严格模式下就会报错哦
      enumerable: false, // 该属性没法被遍历。ps 有些方法仍是能够获取到的,例如 Reflect.ownKeys(),Object.getOwnPropertyNames/getOwnPropertySymbols()
    }
    复制代码
  2. 互斥属性描述符,如下两组属性描述符互斥

    • get、set:对获取、设置属性拦截

      {
        get(){},
        set(){},
      }
      复制代码
    • writable、value

      {
          writable: false, // 该属性没法被修改,若是进行修改则静默错误。ps 严格模式下就会报错哦
          value: xx,
        }
      复制代码
存在性检测

如下方法对对象的属性、方法进行存在检测:

是否检测原型 是否包含 enumerable=false 是否包含 symbol
in
Reflect.has
hasOwnProperty
迭代

如下方法对对象的迭代

是否遍历原型 是否遍历 enumerable=false 是否遍历 symbol
for...in
Object.keys()
getOwnPropertyNames
getOwnPropertySymbols
Reflect.ownKeys()

Javascript 的原型

每一个 Javascript 对象,都有一个 [[Prototype]] 的特殊属性, 这就是对象的原型。[[Prototype]] 本质上是对其余对象的引用

在有的浏览器,可使用 __proto__ 访问该属性,好比 Google,但按照 ECMAScript 标准,则需使用 Object.getPrototypeOf() 访问

原型链:每一个对象都存在特殊属性 [[Prototype]],[[Prototype]] 指向另外一个对象,另外一个对象也存在 [[Prototype]] 属性...直到为 null 结束,由此对象组成的原型连接就是原型链

原型链查找:在 [[GET]] 时,若是当前对象不存在该属性,且该对象的 [[Prototype]] 不为空,则会继续沿着 [[Prototype]] 引用的对象继续查找,直到找到该属性或对象为空为止

prototype 和 [[Prototype]] 区别

函数也存在 prototype 属性,且在 new 构造调用时,生成的对象能够访问 prototype 属性、方法,因为都叫原型,因此这里对他们进行区分:

prototype 用于构造函数中,用于模拟的
[[Prototype]]用于对象中,用于实例。ps 函数也是对象,全部函数即有 prototype,也有 [[Prototype]]

function Person {}
Person.prototype.xx = xx;

// person 经过 [[Prototype]] 指向 Person.prototype ,从而访问 prototype 对象
var person = new Person();
复制代码

Javascript 的"类"

在 ES6 以前,Javascript 没有类的概念,对象都是由 new 构造调用 构造函数 生成对象。在 ES6 以后,可使用 class 了,那么是否是意味着 Javascript 在 ES6 后就有类了呢?

Javascript "类" 和其余语言类的区别

这里首选明确一个概念:Javascript 没有类,Javascript 中的 class 也只是模拟类的而已(可使用 typescript 或 babel 编译一下,查看编译后的代码)。也能够这样理解,Javascript 一直使用语法糖来装成有类的样子,其实并无。下面会从几个方面来讲明这个

实例化:类 => 对象

:在面向对象中,类是一个模具,经过模具生成事物的步骤叫作实例化,例如 new Person()。生成对象后对象和类互不影响,且对象之间互不影响
Javascript"类":Javascript 生成对象时,依靠的是构造调用,而非类的实例化,例如 new Person()。Javascript 生成对象不须要依靠类,而是直接生成,生成后,经过 [[Prototype]] 来模拟类,因为 [[Prototype]] 是 prototype 的引用,因此对象和类、对象之间能够互相影响

// cpp类

class Person {
 public:
  int age;

  // 构造函数
  Person(int _age) { this->age = _age; }
};

Person *p = new Person(22);
复制代码
// js"类"

// 构造函数
function Person(age) {
  this.age = age;
}
Person.prototype.getAge = function () {
  return this.age;
};
Person.prototype.ind = [1, 2, 3];

var p = new Person(22); // new 为构造调用
p.ind.push(4);

var p2 = new Person(23);
p2.ind; // [1, 2, 3, 4],对象之间互相影响了
复制代码

Javascript new 简单实现

function newSelf(construct, ...args) {
  var sc = Object.create(construct.prototype); // [[Prototype]]赋值
  // 也可使用 var sc = Object.setPrototypeOf({}, construct.prototype);
  var result = construct.call(sc, ...args); // 构造函数运行

  return typeof result === 'object' && result !== null ? result : sc;
}
复制代码
继承:父类 => 子类

:继承后,子类继承父类的属性和方法
Javascript"类":继承后,子类并非继承父类的属性和方法,而是依靠 [[Prototype]] 去访问父类的 prototype

// cpp 类继承
class YellowPerson : public Person {
 public:
  YellowPerson(int _age) : Person(_age) { this->age = _age; }
};

YellowPerson *yp = new YellowPerson(22);
复制代码
function YellowPerson(age) {
  Person.call(this, age); // 构造函数继承
}

// 原型继承
YellowPerson.prototype = new Person();
YellowPerson.prototype.constructor = YellowPerson;

var yp = new YellowPerson(22);
复制代码
多态

:经过函数重载实现多态
Javascript"类":经过 [[GET]] 操做时,若是已找到该属性,则不会沿着 [[Prototype]] 继续查询的特性

Javascript"面向委托"

面向委托:某些对象在自身没法寻找属性和方法时,把该请求委托给另外一个对象《你不知道的 Javascript》。

var person = {
  showAge() {
    return this.age;
  },
};

var yellowMan = Object.create(Person);
yellowMan.age = 22; // yellowMan 本身不包含 age 属性,依靠 [[Prototype]] 访问 person 的 age 属性
复制代码

我的感受面向委托在 Javascript 中和面向对象表现差很少,只是在如下有点小区别:

  • 在面向对象中:利用父类(Person)保存属性和方法,再利用多态来实现不一样的操做
  • 在面向委托中:最好将状态保存在委托者上(YellowPerson),而不是委托对象(Person)

Javascript 的继承

// 父类
function Person(name) {
  this.name = name;
  this.ind = [1, 2, 3];
}
Person.prototype.getName = function () {
  return this.name;
};
复制代码

构造函数继承

function YellowPerson(name) {
  Person.call(this, name);
}
复制代码

优势

  1. 子类之间属性不共用,即便为引用数据类型也是不共用的
  2. 构造函数能够传参数

缺点

  1. 没法获取父类的 prototype 上定义的方法和属性

原型链继承

function YellowPerson() {}

YellowPerson.prototype = new Person();
YellowPerson.prototype.constructor = YellowPerson;
复制代码

切记切记不要这样搞 YellowPerson.prototype = Person.prototype,这样会形成子类和父类的 prototype 指向同一个对象,若是子类修改 prototype,则其余类(父类和其余继承过父类的子类)都会发生改变

优势

  1. 可获取父类的 prototype 上定义的方法和属性

缺点

  1. 构造函数没法传参
  2. 子类虽然能访问到父类的属性,但子类共用父类的属性,若是是基本类型还好说,若是引用类型,则会互相影响
  3. 须要修复 constructor 属性

组合继承

function YellowPerson(name) {
  Person.call(this, name);
}
YellowPerson.prototype = new Person();
YellowPerson.prototype.constructor = YellowPerson;
复制代码

优势

  1. 可获取父类的 prototype 上定义的方法和属性
  2. 子类属性不共用,即便为引用数据类型也是不共用的
  3. 构造函数能够传参数

缺点

  1. 父类运行了两次,多余父类实例
  2. 须要修复 constructor 属性

寄生组合继承

function YellowPerson(name) {
  Person.call(this, name);
}
YellowPerson.prototype = Object.create(Person.prototype); // Object.setPrototypeOf 也能够
YellowPerson.prototype.constructor = YellowPerson;
复制代码

优势

  1. 可获取父类的 prototype 上定义的方法和属性
  2. 子类属性不共用,即便为引用数据类型也是不共用的
  3. 构造函数能够传参数
  4. 父类只运行一次,无多余实例

缺点

  1. 须要修复 constructor 属性
相关文章
相关标签/搜索