面向对象的三大特点:

继承、封装、多态

继承:
  • 提到继承,就要波及到类的概念,我的了解:类就是一个或一些事物的独特属性和办法的一个形象
  • 那么继承,就是外表意思,去继承他的父类所形容的那些属性和办法,并且容许它去扩大、重写等等

    封装

就是将零碎模块装箱,暗藏外部实现,只裸露给内部调用接口

多态

就是多种状态,接口的多种不同的实现形式

也就是说,一个父类的办法或者接口,被多个子类继承并且重写了,这样,父类的一个办法就在子类中有了多种表现形式,就造成了多态

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.getPrototypeOfObject.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的区别和关系?