一直在说面向对象,也在说 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();
复制代码
继承:子类继承父类,子类与父类表现得很像(继承了父类的特性和行为),固然子类也能够包含本身的特性和行为;数据结构
// YellowPerson 继承了 Person,则继承了 Person 的特性和行为
// 虽然 YellowPerson 内部未声明任何属性和方法,但它已经具备 name,age,height...
class YellowPerson extends Person {
playTableTennis() {}
}
复制代码
多态:子类重写父类,继承同一个父类的子类对同一个特性或行为会表现得不一样架构
// 子类继承了父类,但子类对同一个特性 talk 表现的不一样,例如能够说中文,能够说非洲语
class YellowPerson extends Person {
talk() {
console.log('说中文');
}
}
class BlackPerson extends Person {
talk() {
console.log('说非洲语');
}
}
复制代码
封装:内部实现细节对外部隐藏,使用属性描述符来控制成员的访问,属性描述符通常有:private、protected、public
函数式编程
class Person {
private assets: number; // 他有不少资产,除了他本身,并不想让任何人知道
protected houseKey: string; // 这我的不想让外人知道本身家的钥匙,除非是本身的家人,例如他的儿子
public name: string; // 他的名字任何人均可以知道
}
复制代码
上面说到 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》。
有几个注意点:
. => PropertyAccessExpression; [] => ElementAccessExpression
对象的获取属性、设置属性操做。这里注意一点,和 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
公共属性描述符: configurable、enumerable
{
configurable: false, // 该属性没法被改变,没法删除,没法从新设置属性描述,且对没法进行的操做静默失败。ps 严格模式下就会报错哦
enumerable: false, // 该属性没法被遍历。ps 有些方法仍是能够获取到的,例如 Reflect.ownKeys(),Object.getOwnPropertyNames/getOwnPropertySymbols()
}
复制代码
互斥属性描述符,如下两组属性描述符互斥
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 对象,都有一个 [[Prototype]]
的特殊属性, 这就是对象的原型。[[Prototype]] 本质上是对其余对象的引用。
在有的浏览器,可使用 __proto__
访问该属性,好比 Google,但按照 ECMAScript 标准,则需使用 Object.getPrototypeOf()
访问
原型链:每一个对象都存在特殊属性 [[Prototype]],[[Prototype]] 指向另外一个对象,另外一个对象也存在 [[Prototype]] 属性...直到为 null 结束,由此对象组成的原型连接就是原型链
原型链查找:在 [[GET]] 时,若是当前对象不存在该属性,且该对象的 [[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();
复制代码
在 ES6 以前,Javascript 没有类的概念,对象都是由 new
构造调用 构造函数 生成对象。在 ES6 以后,可使用 class
了,那么是否是意味着 Javascript 在 ES6 后就有类了呢?
这里首选明确一个概念: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》。
var person = {
showAge() {
return this.age;
},
};
var yellowMan = Object.create(Person);
yellowMan.age = 22; // yellowMan 本身不包含 age 属性,依靠 [[Prototype]] 访问 person 的 age 属性
复制代码
我的感受面向委托在 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);
}
复制代码
优势:
缺点:
function YellowPerson() {}
YellowPerson.prototype = new Person();
YellowPerson.prototype.constructor = YellowPerson;
复制代码
切记切记不要这样搞 YellowPerson.prototype = Person.prototype
,这样会形成子类和父类的 prototype 指向同一个对象,若是子类修改 prototype,则其余类(父类和其余继承过父类的子类)都会发生改变
优势:
缺点:
function YellowPerson(name) {
Person.call(this, name);
}
YellowPerson.prototype = new Person();
YellowPerson.prototype.constructor = YellowPerson;
复制代码
优势:
缺点:
function YellowPerson(name) {
Person.call(this, name);
}
YellowPerson.prototype = Object.create(Person.prototype); // Object.setPrototypeOf 也能够
YellowPerson.prototype.constructor = YellowPerson;
复制代码
优势:
缺点: