温故知新之javascript面向对象

基本概念

类和实例是大多数面向对象编程言语的基本概念

  • 类:类是对象的范例模板

  • 实例:实例是依据类建立的对象
    然则,JavaScript言语的对象系统,不是基于“类”的,而是基于组织函数(constructor)和原型链(prototype)。了与一般函数辨别,组织函数名字的第一个字母一般大写。

组织函数的特性有两个。

  • 函数体内部运用了this关键字,代表了所要天生的对象实例。

  • 天生对象的时刻,必需用new敕令

new与组织函数

  1. new敕令本身就能够实行组织函数,所以背面的组织函数能够带括号,也能够不带括号。

    下面两行代码是等价的。

    var v = new Vehicle();
    var v = new Vehicle;

    应当异常警惕,防止涌现不运用new敕令、直接挪用组织函数的状况。为了保证组织函数必需与new敕令一同运用,一个解决办法是,在组织函数内部运用严厉情势,即第一行加上use strict

    道理:由于在严厉情势中,函数内部的this不能指向全局对象,默许即是undefined,致使不加new挪用会报错(JavaScript不允许对undefined增加属性)。

  2. new敕令的道理

    • 建立一个空对象,作为将要返回的对象实例

    • 将这个空对象的原型,指向组织函数的prototype属性

    • 将这个空对象赋值给函数内部的this关键字

    • 最先实行组织函数内部的代码

    即:

    var obj  = {};
    obj.__proto__ = Base.prototype;
    Base.call(obj);
  3. 组织函数的return
    假如组织函数内部有return语句,而且return背面随着一个对象,new敕令会返回return语句指定的对象;不然,就会不论return语句,返回this对象。

  4. 假如对一般函数(内部没有this关键字的函数)运用new敕令,则会返回一个空对象
    这里碰到了一个题目,题目形貌以下一般函数用new测试的时刻箭头函数报错了

建立对象

JavaScript对每一个建立的对象都邑设置一个原型,指向它的原型对象。
当我们用obj.xxx接见一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,假如没有找到,就到其原型对象上找,假如还没有找到,就一向上溯到Object.prototype对象,末了,假如还没有找到,就只能返回undefined
比方,建立一个Array对象:
var arr = [1, 2, 3];
其原型链是:
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype定义了indexOf()、shift()等要领,因而你能够在所有的Array对象上直接挪用这些要领。
很轻易想到,假如原型链很长,那末接见一个对象的属性就会由于花更多的时候查找而变得更慢,因而要注意不要把原型链搞得太长。

new Student()

function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
}
var xiaoming= new Student('xiaoming'),
    xiaohong= new Student('xiaohong');

xiaoming ↘
xiaohong -→ Student.prototype —-> Object.prototype —-> null
xiaojun ↗
用new Student()建立的对象还从原型上获得了一个constructor属性,它指向函数Student本身:

xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true

Object.getPrototypeOf(xiaoming) === Student.prototype; // true

xiaoming instanceof Student; // true

constructor

constructor属性的作用,是辨别原型对象究竟属于哪一个组织函数。
function F() {};
var f = new F();

f.constructor === F // true
f.constructor === RegExp // false
上面代码示意,运用constructor属性,肯定实例对象f的组织函数是F,而不是RegExp。

组织函数继承 VS 原型链继承

xiaoming.name
//"xiaoming"
xiaohong.name
//"xiaohong"
xiaoming.hello
/*function() {
        alert('Hello, ' + this.name + '!');
    }
*/
xiaohong.hello
/*function() {
        alert('Hello, ' + this.name + '!');
    }
*/
xiaoming.hello === xiaohong.hello
//false

xiaoming和xiaohong各自的name差别,这是对的,不然我们没法辨别谁是谁了。

xiaoming和xiaohong各自的hello是一个函数,但它们是两个差别的函数,虽然函数称号和代码都是雷同的!

假如我们经由过程new Student()建立了许多对象,这些对象的hello函数实际上只须要同享同一个函数就能够了,如许能够节约许多内存。

要让建立的对象同享一个hello函数,依据对象的属性查找准绳,我们只要把hello函数移动到xiaoming、xiaohong这些对象配合的原型上就能够了,也就是Student.prototype

修正代码以下:

function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};
xiaoming.hello === xiaohong.hello
//true

继承体式格局对照

  1. 借用组织函数继承
    基本思想很简单,在子范例的组织函数内部挪用父范例的组织函数:

    function SuperType(){
     this.colors = ["red", "blue", "green"];
    }
    function SubType(){
         //继承了 SuperType
         SuperType.call(this);
    }
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    var instance2 = new SubType();
    alert(instance2.colors); //"red,blue,green"

    题目:要领都在组织函数内部定义,函数的复用性就无从谈起了。在超范例的原型中定义的要领,对子范例而言是不可见的。斟酌这些题目,借用组织函数也是很少零丁运用。

  2. 组合继承
    完成的思绪是运用原型链完成对原型属性和要领的继承,而经由过程constructor stealing手艺完成对实例属性的继承。

    function SuperType(name){
         this.name = name;
         this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function(){
         alert(this.name);
    };
    function SubType(name, age){
         //继承属性
         SuperType.call(this, name);
         this.age = age;
    }
    
    //继承要领
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
         alert(this.age);
    };
    
    var instance1 = new SubType("Nicholas", 29);
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    instance1.sayName(); //"Nicholas";
    instance1.sayAge(); //29
    
    var instance2 = new SubType("Greg", 27);
    alert(instance2.colors); //"red,blue,green"
    instance2.sayName(); //"Greg";
    instance2.sayAge(); //27

    组合继承防止了原型链和借用组织函数的缺点,融会二者之长,是最经常使用的JS继承情势。

  3. 原型式继承
    假如只是想让一个对象与另一个对象坚持相似的状况下,没有必要调兵遣将地建立组织函数。我们能够运用原型式继承。
    Rect.prototype = Object.create(Shape.prototype);

  4. 原型继承
    JavaScript的原型继承完成体式格局就是:

  • 定义新的组织函数,并在内部用call()挪用愿望“继承”的组织函数,并绑定this;

  • 借助中心函数F完成原型链继承,最好经由过程封装的inherits函数完成;

  • 继承在新的组织函数的原型上定义新要领。

    var print = require('./print.js');
    
    function Student(props) {
        this.name = props.name || 'Unnamed';
    }
    
    Student.prototype.hello = function () {
        print('Hello, ' + this.name + '!');
    };
    
    function Sub(props) {
        Student.call(this, props);
        this.grade = props.grade || 1;
    }
    
    Sub.prototype.getGrade = function() {
        return this.grade;
    };
    
    function inherits(Child, Parent) {
        var F = function() {};
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Child;
    }
    
    inherits(Sub, Student);
    
    var xiaoming = new Sub({
        name: 'xiaoming',
        grade: 100
    });
    
    print(xiaoming.name + ' ' + xiaoming.grade);
    
    print(xiaoming instanceof Student);
    print(xiaoming instanceof Sub);

class继承

class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}
class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super挪用父类的组织要领!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

ES6引入的class和原有的JavaScript原型继承有什么辨别呢?实际上它们没有任何辨别,class的作用就是让JavaScript引擎去完成本来须要我们本身编写的原型链代码。简而言之,用class的优点就是极大地简化了原型链代码。
这里碰到的题目是isPrototypeOf的题目

多重继承

JavaScript不供应多重继承功用,即不允许一个对象同时继承多个对象。然则,能够经由过程变通要领,完成这个功用。

function M1() {
  this.hello = 'hello';
}

function M2() {
  this.world = 'world';
}

function S() {
  M1.call(this);
  M2.call(this);
}
S.prototype = M1.prototype;

var s = new S();
s.hello // 'hello'
s.world // 'world'
s instanceof M2
//false

上面代码中,子类S同时继承了父类M1和M2。固然,从继承链来看,S只要一个父类M1,然则由于在S的实例上,同时实行M1和M2的组织函数,所以它同时继承了这两个类的要领。

扩大

  1. apply的运用:转换相似数组的对象

    Array.prototype.slice.apply({0:1,length:1})
    // [1]
    
    Array.prototype.slice.apply({0:1})
    // []
    
    Array.prototype.slice.apply({0:1,length:2})
    // [1, undefined]
    
    Array.prototype.slice.apply({length:1})
    // [undefined]
  2. bind连系call,能够改写一些JavaScript原生要领的运用情势

    [1, 2, 3].slice(0, 1)
    // [1]
    
    // 等同于
    
    Array.prototype.slice.call([1, 2, 3], 0, 1)
    // [1]

    call要领实质上是挪用Function.prototype.call要领,因而上面的表达式能够用bind要领改写。

    var push = Function.prototype.call.bind(Array.prototype.push);
    var pop = Function.prototype.call.bind(Array.prototype.pop);
    
    var a = [1 ,2 ,3];
    push(a, 4)
    a // [1, 2, 3, 4]
    
    pop(a)
    a // [1, 2, 3]
  3. 某个属性究竟是原型链上哪一个对象本身的属性。

    function getDefiningObject(obj, propKey) {
      while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
        obj = Object.getPrototypeOf(obj);
      }
      return obj;
    }
  4. 猎取实例对象obj的原型对象,有三种要领。

    obj.__proto__
    obj.constructor.prototype
    Object.getPrototypeOf(obj)

    引荐末了一种

    面向对象觉得还没怎么搞邃晓,模块的东西还没弄邃晓,有时候补上

参考资料

廖雪峰先生的教程
阮一峰先生的教程
BruceYuj的博客

    原文作者:jasminecjc
    原文地址: https://segmentfault.com/a/1190000007089886
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞