面向对象的三大特点:
继承、封装、多态
继承:
- 提到继承,就要波及到 类的概念,我的了解:类就是一个或一些事物的独特属性和办法的一个形象
-
那么继承,就是外表意思,去继承他的父类所形容的那些属性和办法,并且容许它去扩大、重写等等
封装
就是将零碎模块装箱,暗藏外部实现,只裸露给内部调用接口
多态
就是多种状态,接口的多种不同的实现形式
也就是说,一个父类的办法或者接口,被多个子类继承并且重写了,这样,父类的一个办法就在子类中有了多种表现形式,就造成了多态
JS 的面向对象
概述
- 面向对象是一种思维,或者说编程模式
-
它与面向过程不同,面向过程关注的是倒退的过程,而面向对象关注的是参与者是谁,如何参加的?
JS 面向对象机制的 前(li)世 (shi) 今(yi)生(liu)
- 对于 JS 来说,所有皆对象。这是有肯定的历史起因的 – 因为 JS 的作者 Brendan Eich 在过后开发 JS 时,面向对象的编程思维正泛滥
-
过后的浏览器十分高级,以至于 Brendan Eich 不想把 JS 写得那么正式 / 简单,它只须要满足最简略的浏览器 - 用户的交互需要即可,比方 – 填写一个用户名
Brendan Eich 的做法 1 –new 构造函数
- 他参考了一下其余语言,发现都是通过 new 类的构造函数来实现的继承,而后 – 他就采纳了一种简化的形式:摈弃类,容许在 JS 中间接 new 构造函数失去实例对象,以此实现继承。
诶,有类我不写,就是玩~
-
然而 – 这种办法有一个 毛病 – 无奈共享属性和办法,或者说这种办法创立的实例对象继承的只是值,而不是地址
- 这意味着如果要继承的属性有 5 个,每个占内存 10,若须要创立 100 个实例对象,那么光是这些属性所耗费的内存就是:510100,对性能很不敌对
- 除此之外,如果一个实例对象增加或批改一个属性,影响不到其余的实例对象。
为了解决这个毛病 –
Brendan Eich 的做法 2 – 引入 prototype 属性 - 为 构造函数 设置一个 prototype 属性, 专门用来 寄存 实例对象们可能共享的属性和办法,那些不须要共享的属性和办法,就还是放在构造函数外面。
-
这样子,实例对象的属性和办法就被分成两种,一种是不共享的,另一种是共享的的。
new 关键字齐全继承,
- 它其实是一个被封装的函数,其外围机制是 setPrototypeOf() 和apply()
- 当 new 一个实例对象时,将会 通过调用 setPrototypeOf()“主动援用 ”prototype 对象的属性和办法,(获取到 prototype 对象里寄存的属性和办法)
-
而后调用 apply()办法,(获取到构造函数中寄存的属性和办法)
这就是原型模式
- 用于 创立实例对象 ,实现了 高性能继承。
- 在须要继承时,容许一个对象间接克隆一个已有的原型对象,以此疾速地生成和它一样的新对象实例,所有 对象实例会共享原型对象的所有属性和办法
明确了原型模式就很容易了解原型和原型链了 –
原型和原型链
原型 prototype
每个函数 (构造函数) 都有一个 prototype 属性,它自身是个援用,指向一个对象,叫做 原型对象 ,其中蕴含了能够由 由同一个构造函数创立的所有实例对象 共享的属性和办法
能够简略了解:原型就是一个模板,能够通过克隆它实现继承
Tips:一个构造函数(包含它本人),和由它创立的所有实例对象都以援用的形式,共用一个原型对象,(因为在 JS 中,函数自身是对象)
隐式原型__proto__
- 每个 对象 都有一个__proto__属性, 指向 它的构造函数的原型对象
- 即:它的值 === 它的构造函数的
[[Prototype]]
的值
咱们举个栗子,就很好了解了 –
let constructor = function () {};
let obj = new constructor();
console.log(obj.__proto__ === constructor.prototype);
//true
一个大坑 :“__proto__”这个属性的正确写法是 两边是各两个下划线 “_”
ES6 之后更举荐应用Object.getPrototypeOf/Reflect.getPrototypeOf
和Object.setPrototypeOf/Reflect.setPrototypeOf
不举荐间接应用该属性:
为什么?
- 因为__proto__前后的双下划线,阐明它实质上是一个外部属性,而不是一个正式的对外的 API,只是因为浏览器广泛支持,才被退出了 ES6。
-
无论从语义的角度,还是从兼容性的角度,都不要应用这个属性,而是应用上面的 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
Obj.constructor 属性
是另一个对象的属性,它指向该对象的构造函数
function company() {
this.name = "name";
this.address = "address";
}
let obj = {};
Object.setPrototypeOf(obj, company.prototype);
console.log(obj.constructor === company); //true
// 和 obj.__proto__联动一波,增进了解
console.log(obj.__proto__ === obj.constructor.prototype); //true
原型链
每个 对象 都有__proto__属性,来指向 它的构造函数的原型对象,而后 原型对象 也是对象,也领有__proto__属性 … 而后就这样始终往下层指,直到 null
(链的根部或者尾部就是 null),就造成了一条 原型链
原型链查问
当拜访一个对象的属性时,如果该对象外部没有这个属性,那么就会去它的__proto__属性所指向的那个原型对象里找,如果还找不到,就持续往父级的构造函数的原型对象里找 … 直到原型链顶端 null
这个过程和执行上下文、作用域链很像
JS 实现继承的几种形式?
1. 构造函数 +apply 继承
function company() {
this.name = "name";
this.address = "address";
}
let obj = {};
company.apply(obj);
console.log(obj);
//{name: 'name', address: 'address'}
只能继承构造函数里的属性, 不能继承构造函数原型及原型链上的属性,如下↓
function company() {
this.name = "name";
this.address = "address";
}
company.prototype.phone = 110;
let obj = {};
company.apply(obj);
console.log(obj.phone); //undefined
2.prototype 原型继承
原型继承更高效且节俭内存,也更 JS
function company() {
this.name = "name";
this.address = "address";
}
company.prototype.phone = 110;
let obj1 = {};
let obj2 = {};
Object.setPrototypeOf(obj1, company.prototype);
Object.setPrototypeOf(obj2, company.prototype);
console.log(obj1.phone === obj2.phone && obj1.phone === 110);//true
只能继承构造函数的原型对象 (prototype) 和原型链上的属性, 不能继承构造函数外部的属性
console.log(obj1.name); //undefined
3.new 关键字类式继承
概述
-
JS 中其实是没有类的概念的 ,所谓的类是构造函数模仿进去的。当咱们应用 new 关键字的时候,或者 ES6 的 Class 关键字,就感觉“类”的概念很像 java。但其实,new 和 class 关键字底层实现机制还是基于 原型 的,它们都是语法糖。
那么在 JS 中,是如何实现类继承的?
- 就是构造函数当做父类,而后通过 call 和 apply 办法,扭转 this 的作用环境,使得子类可能获取到父类的各种属性。能够说 ES6 引入 call 和 apply 办法一部分起因就是为了更好地面向对象。
留神:
- JS 函数中的 this 对象就像是一个函数的隐式参数,或援用
-
实际上 new 关键字 – 是一个封装好的函数,其外部机制是下面第 1、2 个办法的组合, 因而这种形式交融了二者的长处,能齐全继承 构造函数外部属性 + 原型对象里的属性 + 原型链上属性
function company() { this.name = "Billie"; this.address = "address"; } company.prototype.phone = 110; let obj1 = new company(); let obj2 = new company(); console.log(obj1.phone === obj2.phone && obj1.phone === 110); console.log(obj1.name === obj2.name && obj1.name === "Billie"); // true
来看一个稍简单点的栗子
在函数对象内用过 apply 调用父类的构造函数,使得本身取得父类 (父级构造函数) 的办法和属性**var father = function() { this.age = 52; this.say = function() {alert('hello word'); } } var child = function() { this.name = 'bill'; father.call(this); } var man = new child(); man.say();
在下面代码中,首先,new 关键字外部执行到 – let result = child.apply(man, null);
会调用 child 函数,而后将其中的 this 间接换成 man,就像这样↓
var child = function() {
man.name = 'bill';
father.call(man);
}
var man = new child();
man.say();
而后,执行到了 call 办法,同理,执行 father 办法并将其中的 this 换成 man–
var father = function() {
man.age = 52;
man.say = function() {alert('xxx');
}
}
而后,man 对象曾经存在那些属性和办法了,因而间接调用 man.say()即可
或者 – 灵活运用 Object.create()办法、obj.constructor 属性
Object.create()办法
- 逻辑:该办法会创立一个新对象,并应用传入的对象当做新创建的对象的__proto__,而后返回这个新对象
- 参数:对象
- 返回值:对象
因而,这能够是另外一种创立实例对象的形式,你能够 灵活运用 它
function company() {
this.name = "Billie";
this.address = "address";
}
company.prototype.phone = 110;
let obj1 = Object.create(company.prototype);
// 这样就和第 2 种继承办法一样了
console.log(obj1.__proto__ === company.prototype);
console.log(obj1.phone);
console.log(obj1.name);
//true
//110
//undefined
obj.constructor 属性
而对于 constructor 属性,你能够 灵活运用 该办法来实现 构造函数之间的继承(因为只有函数有这个属性)
function Parent() {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child() {Parent.call(this);
this.type = 'child5';
}
// 产生一个两头对象隔离 `Child` 的 `prototype` 属性和 `Parent` 的 `prototype` 属性援用的同一个原型。Child.prototype = Object.create(Parent.prototype);
// 给 Child 的原型对象从新写一个本人的 constructor。Child.prototype.constructor = Child;
参考:
Javascript 继承机制的设计思维 – 阮一峰
JS 面向对象是什么?
《JS 中 new 操作符做了什么?》–Crushdada’s shimo Notes
对 JS 原型和原型链的了解 –CSDN
JS 原型继承和类式继承
JavaScript 中的 this 是什么?它到底做了什么?– 思否
JS 继承的几种形式
js 中__proto__和 prototype 的区别和关系?