乐趣区

关于javascript:一篇文章搞定JavaScript面向对象

面向对象的三大特点:

继承、封装、多态

继承:

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

    封装

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

多态

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

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

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

退出移动版