面向对象编程是将事物当作一个个对象,对象有本身的属性有本身的方法。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
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
改变Cat
的this
指向,让咱们能够借用Animal
的构造函数,而后再让Cat
的prototype
指向一个Animal
实例,并把constructor
修改正常。
若是咱们初始化一个Cat
类,而后调用say
方法,那么在内部的查找流程是:
自身 -> 沿着[[prototype]]找到Cat.prototype(它是一个Animal实例)-> 沿着Animal实例的[[prototype]]查找 -> 找到Animal.prototype(找到run方法并调用)
咱们发现Cat.prototype = new Animal()
这样就会让Cat
的prototype多出name
和age
两个属性。
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函数对描述的属性。描述符必须是这两种形式之一;不能同时是二者。
configurable
是否能配置此属性,为false
时不能删除,并且再设置时会报错除了Writableenumerable
当且仅当该属性的enumerable
为true
时,该属性才可以出如今对象的枚举属性中value
包含了此属性的值。writable
是否能修改属性值configurable
enumerable
get
读取时调用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
}
});
复制代码
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 能够经过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
构造函数会忽略参数。