JavaScript 面向对象

JavaScript高级语法之面向对象

这篇文章主要讲了JavaScript ES6之后有类的概念的面向对象的方法和ES5之前没有类的概念时的面向对象的方法
如有错误,欢迎前来指正

JavaScript 面向对象(ES6之后)

  • JavaScript中的对象是无序的属性和方法的集合

创建类

  • 同样使用大驼峰命名法,首字母大写
  • 代码示例:
// 声明类
class 类名 {
    // class body
}
// 实例化对象
var 对象名 = new 类名();
  • 在类中所有的函数都不用加 function

构造函数 constructor

  • 使用 new 实例化对象的时候自动调用构造函数
class Name {
    constructor(uname) {
        // 方法体
        this.uname = uname;
    }
}
var name = new Name('菜鸟小铭');
console.log(name.uname);
// 输出:菜鸟小铭

在类中添加方法

  • 直接把方法写进类中就可以了
  • 类中的方法不需要加 function 关键字
  • 多个方法之间不需要加逗号分割
class Person {
    say() {
        console.log('hello');
    }
}

类的继承

  • 使用 extends 关键字来继承
class Father {
    // 父类
}
class Son extends Father {
    // 子类继承父类
}
  • 下面的代码能成功运行吗
class Father {
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x+this.y);
    }
}
class Son extends Father {
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }
}
var son = new Son(1,2);
son.sum();
  • 下面的代码会报错:子类继承而来的方法里面的 this 指向的是父类里面的 x 和 y ,子类里面找不到指向父类的 x 和 y,所以报错

super 关键字

  • super 关键字可以调用父类的所有函数
  • 这样就可以解决上面的问题
  • 修改后的代码:
class Father {
    constructor(x,y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x+this.y);
    }
}
class Son extends Father {
    constructor(x,y) {
    super(x,y); // 调用了父类的构造函数
    }
}
var son = new Son(1,2);
son.sum();
  • 如果子类里的函数和父类有同名函数,子类的函数会覆盖父类的函数

  • super 调用父类函数 super.函数()

  • super()调用的是父类的构造函数

  • 在构造函数里,super 必须在子类 this 之前调用!

  • 例如:

// 某一个子类的构造函数
constructor(x,y) {
    // super 必须在子类 this 之前调用
    super(x,y);
    this.x = x;
    this.y = y;
}

类和对象使用时的注意点

  • ES6 里类没有变量提升,所以要先定义类再实例化对象

  • 类里的属性和方法一定要加 this

  • 案例:现在有一个按钮,我们点击一下就输出一个字

<button>点击button>
<script>
    class Father {
        constructor(uname) {
            this.uname = uname;
            this.btn = document.querySelector('button');
            // 这里的函数不能加括号,因为一加括号他就立即执行了
            this.btn.onclick = this.sing;
        }
        sing() {
            console.log('hello world');

        }
    }
    var father = new Father('菜鸟小铭');
script>
  • 这里面绑定事件的时候千万不要加括号,像这样this.btn.onclick = this.sing()

  • 这样的化他会直接调用这个函数,而不是点击一下调用这个函数

  • this 指向问题

    • constructor 里的 this 指向的是创建的实例对象
    • 普通函数谁调用函数 this 指向谁 (指向方法的调用者)

构造函数和原型(ES5之前)

  • ES6 之前, JavaScript 没有类的概念
  • 目前的浏览器主要支持 ES5 版本,现在主要用构造函数来创建对象
  • 下面的方法都是为了兼容 ES5 以前的版本所作的

创建对象的三种方式

  • 代码示例:
// 通过 new 来创建对象
var obj1 = new Object();

// 通过 字面量 创建对象
var obj2 = {};

// 通过构造函数创建对象
function Star(uname, age) {
    // 变量
    this.uname = uname;
    this.age = age;
    // 方法
    this.sing = function() {
        console.log('我会唱歌');
    }
}
// 创建对象
var noobMing = new Star('菜鸟小铭', 18);

静态成员和实例成员

  • 实例成员:通过 this 添加的成员
    • 只能通过对象来访问 (不能通过构造函数访问)
  • 静态成员:在构造函数本身添加的成员
    • 静态成员只能通过构造函数访问 (不能通过对象来访问)
function Star(uname) {
    // 实例成员
    this.uname = uname;
}
// 静态成员
Star.age = 18;

原型对象 (prototype)

  • 构造方法的问题:浪费内存的问题,复杂数据类型 (例如函数) 他会另外开辟一个内存空间存相同的数据

  • 为了解决这个问题,每个构造函数都会有一个自带的对象 prototype,我们可以把那些不变的方法直接定义在 prototype 上,所有的对象可以共享这些方法

  • 代码示例:

function Star(uname, age) {
    // 变量
    this.uname = uname;
    this.age = age;
}
// 原型对象里的方法
Star.prototype.sing = function() {
    console.log('我会唱歌');
}
  • 还可以直接写 prototype 对象 (相当于复写了这个 prototype 里面一些默认的属性会被覆盖掉)
Star.prototype = {
    sing: function() {
        console.log('我会唱歌');
    }
}

对象原型 (__proto__)

  • 注意前后都是两个下划线

  • 对象都会有这个属性,指向构造函数的 prototype 原型对象,所以我们可以使用构造函数里没有的方法 (有点像双向绑定)

  • __proto__ === prototype

  • 非标准属性,在实际开发的时候不能使用这个属性

constructor 构造函数

  • 对象原型 (__proto__) 和原型对象 (prototype) 中都有这个属性,他指向构造函数本身
  • 记录该对象引用了哪个构造函数

构造函数,实例,原型对象之间的关系

  • 构造函数里面有 prototype 指向原型对象 (也就是放公共函数的地方),实例 (对象) 里面有一个 __proto__ 指向原型对象
    JavaScript 面向对象_第1张图片

原型链

  • 原型对象也是一个对象,按理来说也会有一个 __proto__ 属性
  • 原型对象里面的 __proto__ 指向 Object.prototype
  • Object 的原型对象里面的 __proto__ 指向 null
  • 看起来可能有点绕,下图可能更方便理解一点 (另外可以自己写一个构造函数去实验一下,再对照着下图能更好理解)
    JavaScript 面向对象_第2张图片

原型链成员查找规则

  • 他是按照原型链的顺序来查找
  • 先从对象本身开始查找,如果没有就通过 __proto__ 进入原型对象 (prototype) 上查找有没有这个成员,再没有的话就去 Object.prototype 里面去找,再找不到的话就返回 null (undefined)
  • 查找的时候遵循就近原则

原型对象里的 this 指向

  • 构造函数中 this 指向创建出的对象 (如果只输出 this 的话 this 实际上指向的是 Father 这个构造函数)
  • 原型对象 (prototype) 的 this 指向实例化对象

扩展内置对象

  • 扩展内置对象 (例如数组,字符串等) 自带的方法

使用 Array.prototype 就可以查看里面的方法

  • 我们可以这样添加方法 (例如给数组对象添加一个数据求和的方法)
Array.prototype.sum = function() {
    var sum = 0;
    for(var i = 0; i < this.length; i++) {
        sum += this[i];
    }
    return sum;
}
  • 同时注意不要将默认方法给覆盖掉

组合继承

  • ES5 没有 extends 关键字,所以要用其他方法来模拟继承的方式

call 函数

  • call 可以调用函数
  • call 改变函数的 this 指向 (第一个参数代表函数的 this 指向,后面的参数代表传递参数)
  • 代码示例:
// 普通函数
function fn(x, y) {
    console.log(this);
    console.log(x + y);
}
var o = {
    name: "菜鸟小铭"
};
// 1. call 调用函数
fn.call();
// 2. call 改变 this 的指向
fn.call(o, 1, 2);
// 输出 this 指向 o,第二行输出 3

借用构造函数继承父类属性

  • 用 call 函数来调用父构造函数,但是需要把父构造函数里面的 this 都变为子构造函数的 this
function Father(uname, age) {
    // this 指向 Father
    this.uname = uname;
    this.age = age;
}
function Son(uname, age) {
    // 使 Son 继承 Father 的属性
    Father.call(this, uname, age);
}

借用构造函数继承父类方法

  • 父类的方法是写在 prototype 里面的,光凭上面的调用函数是继承不过来父类的方法

  • 同样不能让父原型对象直接等于子原型对象,这样我们再往子原型对象中添加方法时父原型对象也会添加一样的方法 (这个的原因和实参差不多,父原型对象的地址给了子原型对象,当子原型对象添加方法时,父原型对象因为使用的是同一个地址所以也会发生改变)

  • 实际使用时让 Father实例对象 来当一个中间站,然后再赋值给 son 的原型对象

Son.prototype = new Father();
  • 由于 Son原型对象 指向的是父类的对象里的__proto__,所以并不会影响到父类的原型对象

JavaScript 面向对象_第3张图片

  • 但是这种方法因为是将 Father 里的所有内容覆盖过来,所以里面的 constructor 也会指向 Father
  • 需要手动修改过来 Son.prototype.constructor = Son

你可能感兴趣的:(JavaScript学习,js,javascript,面向对象编程)