JS 中的面向对象 prototype class

面向对象编程是将事物当作一个个对象,对象有本身的属性有本身的方法。javascript

好比人,咱们先定义一个对象模板,咱们能够定义一些属性 好比,名字年龄和功能,好比走路。咱们把这个叫作类。java

而后帮们将具体数据传入模板,成为一个个具体的人,咱们将它叫作实例。编程

JS 中面向对象是使用原型(prototype)实现的。数组

function Person(name, age) {
    this.name = name
    this.age = age
    this.walk = function(){}
}

Person.prototype.walk = function () {}

var bob = new Person('bob', 10)
console.log(bob.age)
复制代码

其中的Person函数叫作构造函数,构造函数通常会将第一个字母大写, 构造函数建立特定类型的对象,构造函数中没有,显式的建立对象,和返回对象,直接将属性赋值给 this浏览器

咱们使用new关键字建立对象实例,它会经历 4 个步骤,bash

  1. 建立一个新对象
  2. 将构造函数的的做用域赋给新对象
  3. 执行代码
  4. 返回新对象,实例会保存着一个 constructor 属性,该属性指向构造函数

咱们也能够将walk函数写在构造函数中this.walk=function(){},可是这样写的话,每新建一个实例,实例都会新建一个walk函数,这样就浪费内存空间,咱们将它放在prototype上这样就会让全部实例共享一个walk函数,可是若是都写了它会调用本身的walk函数而不是共享的。app

每个函数都有一个prototype属性,函数的prototype对象上的属性方法,全部实例都是共享的。函数

prototype对象有个constructor属性,它指向它的构造函数。ui

当建立一个实例时,实例内有会有个[[Prototype]]指针指向构造函数的原型对象,在浏览器中查看显示为__proto__属性。this

当实例访问一个属性或者调用一个方法,好比bob.walk(),内部会首先在自身上查找这个方法,若是找到的话就完成,若是没有找到的话,就会沿着[[prototype]]向上查找,这就是为何prototype上的方法都是共享,若是沿着[[prototype]]找到头,还没找到,那么就会报错bob.walk不是一个函数。

继承

继承主要是利用原型链,让子类的prototype等于父类的实例,也就是利用实例寻找属性和方法时,会沿着[[prototype]]向上找。

继承就是,一个子类继承父类的代码,而不用从新编写重复的代码。好比咱们要写Cat, Dog等类,咱们发现每一个类都有相似this.name = name; this.age = age这些重复的代码,因此咱们能够先写一个Animal类,让Cat,Dog继承这个类,咱们就不用编写重复的属性和方法了。

function Animal(name) { this.name = name; this.age = 10 }
Animal.prototype.say = function () {
    console.log(this.name)
}
function Cat() { Animal.apply(this, arguments) }
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
复制代码

咱们用apply改变Catthis指向,让咱们能够借用Animal的构造函数,而后再让Catprototype指向一个Animal实例,并把constructor修改正常。

若是咱们初始化一个Cat类,而后调用say方法,那么在内部的查找流程是:

自身 -> 沿着[[prototype]]找到Cat.prototype(它是一个Animal实例)-> 沿着Animal实例的[[prototype]]查找 -> 找到Animal.prototype(找到run方法并调用)

咱们发现Cat.prototype = new Animal()这样就会让Cat的prototype多出nameage两个属性。

function Animal(name) { this.name = name; this.age = 10 }
Animal.prototype.say = function () {
    console.log(this.name)
}
function Cat() { Animal.apply(this, arguments) }

function F(){}
F.prototype = Animal.prototype

Cat.prototype = new F()
Cat.prototype.constructor = Cat
复制代码

咱们使用了一个中间类函数F,让它的prototype等于父级的prototype,那么咱们查找到F.prototype时,就自动到了Animal.prototype上。

咱们若是想知道一个属性是否是属于自身而不是来自原型链则可使用

实例.hasOwnProperty(属性) 查看该属性是否来自自己。

Object.getOwnPropertyNames(obj) 返回全部对象自己属性名数组,不管是否能枚举

属性 in 对象 判断可否经过该对象访问该属性,不管是在自己仍是原型上

若是咱们想获取一个对象的prototype,咱们可使用

Object.getPrototypeOf(obj) 方法,他返回对象的prototype

Object.setPrototypeOf(object, prototype) 方法,设置对象的prototype

还可使用对象的__proto__属性获取和修改对象的prototype(不推荐)

属性描述符

在 js 中定义了只有内部才能用的特性,描述了属性的各类特性。

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具备值的属性,该值多是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是二者。

数据属性

  1. configurable 是否能配置此属性,为false时不能删除,并且再设置时会报错除了Writable
  2. enumerable 当且仅当该属性的enumerabletrue时,该属性才可以出如今对象的枚举属性中
  3. value 包含了此属性的值。
  4. writable 是否能修改属性值

存取描述符

  1. configurable
  2. enumerable
  3. get 读取时调用
  4. set 写入时调用

咱们可使用Object.defineProperty方法定义或修改一个对象属性的特性。

var obj = {}

Object.defineProperty(obj, "key", {
  enumerable: false, // 默认为 false
  configurable: false, // 默认为 false
  writable: false, // 默认为 false
  value: "static" // 默认为 undefined
});

Object.defineProperty(obj, 'k', {
    get: function () { // 默认为 undefined
        return '123'
    },
    set: function (v) {
        this.kk = v
    } // 默认为 undefined
})
复制代码

使用Object.getOwnPropertyDescriptor能够一次定义多个属性

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
});
复制代码

class

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,做为对象的模板。经过class关键字,能够定义类。

这样编写面向对象就更加的简单。

和类表达式同样,类声明体在严格模式下运行。构造函数是可选的。

类声明不能够提高(这与函数声明不一样)。

class Person {
    age = 0 // 属性除了写在构造函数中也能够写在外面。
    static a = 0 // 静态属性

    constructor (name) { 
    // 构造函数,可选(若是没有显式定义,一个空的constructor方法会被默认添加)
        this.name = name
    } 
    
    // 类的内部全部定义的方法,都是不可枚举的
    say () { // 方法 共享函数
        return this.name
    }
    
    static walk() { // 静态方法
        
    }
}

typeof Person // "function"
Person === Person.prototype.constructor // true
复制代码

使用的时候,也是直接对类使用new命令,跟构造函数的用法彻底一致,可是忘记加new会报错。

静态属性和静态方法,是属于类的,而不是属于实例的,要使用Person.walk()调用。

类的全部方法都定义在类的prototype属性上面。

// 上面等同于

Person.prototype = {
  constructor() {},
  say() {}
};
Person.a = 0
Person.walk = function () {}
复制代码

ES6 为new命令引入了一个new.target属性,该属性通常用在构造函数之中,返回new命令做用于的那个构造函数。若是构造函数不是经过new命令或Reflect.construct()调用的,new.target会返回undefined,所以这个属性能够用来肯定构造函数是怎么调用的。

function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}
复制代码

Class 内部调用new.target,返回当前 Class

与函数同样,类也可使用表达式的形式定义。

const AA = class A {}
// 这个类的名字是A,可是A只在内部用,指代当前类。在外部,这个类只能用AA引用
const BB = class {}

let person = new class { // 当即执行的 Class
  constructor(name) {
    this.name = name;
  }
}('张三');
复制代码

Class 继承

Class 能够经过extends关键字实现继承。

class Animal {
    constructor (name) {
        this.name = name
    }
}

class Cat extends Animal {
    constructor (...args) {
        super(...args) // 调用父类的 constructor 方法
                        // 必须调用且放在 constructor 最前面
    }
}
复制代码

若是子类没有定义constructor方法,这个方法会被默认添加。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
复制代码

父类函数的静态属性和方法也会继承

super这个关键字,既能够看成函数使用,也能够看成对象使用。

super做为函数时,只能用在子类的构造函数之中,用在其余地方就会报错。

super做为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

在子类普通方法中经过super调用父类的方法时,方法内部的this指向当前的子类实例。

构造函数方法是不能继承原生对象的,

Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
复制代码

可是 class 能够继承。这样就能够构造本身的Array子类。

能够继承了Object,可是没法经过super方法向父类Object传参。这是由于 ES6 改变了Object构造函数的行为,一旦发现Object方法不是经过new Object()这种形式调用,ES6 规定Object构造函数会忽略参数。

相关文章
相关标签/搜索