原型链与继承,类,面向对象
原型与原型链
对象与构造函数
构造函数创建实例对象,对象都是由函数创建出来的
构造函数有prototype
属性,指向构造函数的原型对象
对象有__proto__
属性,指向创建该对象的构造函数的原型对象
原型对象有constructor
属性,指向构造函数
// 构造函数
function Person(name){
this.name = name;
}
// 实例对象
let person = new Person('zsq');
// 二者关系
console.log(person.__proto__ == Person.prototype); // true
console.log(Person.prototype.constructor == Person); // true
函数也是对象,所以也有__proto__
属性,对象都是由函数创建出来的,所以函数对象也有构造函数
函数对象的构造函数是Function()
,所以函数对象的__proto__
属性指向Funtion
的原型对象
console.log(Person.__proto__ == Function.prototype) // true
所以函数既有prototype属性,又有__proto__属性
原型对象也是对象,所以也有__proto__
属性,对象都是由函数创建出来的,所以原型对象也有构造函数
原型对象的构造函数是Object()
,所以原型对象的__proto__
属性指向Object
的原型对象
console.log(Person.prototype.__proto__ == Object.prototype) // true
Object()
是函数,也是对象,函数对象的构造函数是Function()
,函数对象的__proto__
属性指向Funtion
的原型对象
console.log(Object.__proto__ == Function.prototype) // true
Function()
的原型对象也是对象,其构造函数是Object()
,所以其__proto__
属性指向Object.prototype
console.log(Function.prototype.__proto__ == Object.prototype) // true
Object()
的原型对象也是对象,所以也有__proto__
属性,指向null
,null
是原型链的顶端
console.log(Object.prototype.__proto__ == null) // true
总结
- 对象由构造函数创建,有
__proto__
属性,指向创建该对象的构造函数的原型对象 - 函数有
prototype
属性,指向原型对象,同时函数也是对象,有__proto__
属性,函数对象的构造函数是Function()
- 原型对象有
constructor
属性,指向构造函数,同时原型对象也是对象,满足第1点 - 一切对象都继承自Object,一切函数对象都继承自Function(Object也是一种构造函数),null是原型链的顶端
函数与类
函数既可以作为函数,也可以作为对象,既有prototype
属性,也有__proto__
属性
类实际上就是构造函数,但类构造函数与类不是一回事
可以将类理解为函数,区别是函数在声明是可以提升,函数收到函数作用域限制,类声明时不可提升,类受到块级作用域限制
class Person{}
let p = new Person();
let p_ = new Person.constructor();
console.log(p === p_); // false
类包含构造函数方法constructor
,实例方法,获取函数get()
,设置函数set()
,静态类方法static
类构造函数与构造函数的区别
调用类构造函数必须使用new
操作符否则会报错,而普通构造函数如果不使用new
调用,则会以全局的this
作为内部对象
类与函数的属性和方法定义的位置
类构造函数定义的属性和方法会创建在实例对象中,在类块中定义的方法会定义在类的原型上,在类块中不能定义数据属性会报错(访问器属性可以),在类块中定义的静态属性(等于=)和方法会定义在类(函数)本身上
静态方法适合作为工厂,处理多个对象
class Person{
static name = 'qioqio' // 静态属性,定义在类上
name_ = 'qio' // 定义在实例对象上
constructor(){
// 定义在实例对象上
}
sayName(){} // 定义类原型的方法
static getName(){} // 静态方法,定义在类上
}
function Person_(name){
this.name = name; // 定义在实例对象上
name = 'qioqio'; // 静态属性,定义在函数对象上
getName(){}; // 静态方法,定义在函数对象上
}
类的私有属性
私有属性
class
内部不同方法间可以使用,因此this
要指向实例化对象- 不能被外部访问,因此实例化对象
person.name
既不能获得值,也不能设定值,应该返回undefined
,甚至应该在实例化之后,并不知道有name
这个属性存在,开发者甚至可以自己再person.name = 'new name'
动态去创建一个非私有属性 - 不能被继承,因此
extends
后子类不具备该属性 - 方便的调用方式,比如类
this._name
形式
创建
无法完全实现私有属性:闭包,Symbol,WeakMap
Symbol
只要不在立即执行函数return _id
,就可以实现无法直接访问该值,但如果直接打印实例对象还是能在控制台查看到,并且使用Object.getPropertySymbols()
也能看到
WeakMap
是将私有属性和私有方法保存到WeakMap
中,由于该类型创建在类外,所以不属于实例的属性和方法,由此实现私有,只能通过访问器属性访问
完全实现私有属性:es12新增的**#**
var Person = (function () {
const _id = Symbol('id'); // 只要不把_id返回到全局作用域,就无法直接访问该值
const _private = new WeakMap(); // 将属性和方法保存到WeakMap中,不属于实例的属性和方法
const _privateMethod = new WeakMap();
class Person {
#score; // 声明私有属性
constructor(name, age, tel) {
this.name = name
this.age = age
this._phone = tel
this[_id] = 1
_private.set(this, 'url:https')
_privateMethod.set(this, function (newUrl) {
_private.set(this, newUrl)
})
this.#score = 10 // 真正意义的私有属性
}
get id() { // 利用访问器属性访问函数内部作用域
return this[_id];
}
get private() {
return _private.get(this);
}
get privateMethod() {
return _privateMethod.get(this);
}
}
return Person; // 把函数内部作用域的类返回到全局作用域里
}())
var p = new Person('zsq', 21, '15014329752');
console.log(p); // Person { name: 'zsq', age: 21, _phone: '15014329752', [Symbol(id)]: 1 }
// console.log('p[_id]', p[_id]); // ReferenceError: _id is not defined
console.log('p.score', p.score); // undefined
console.log('id', p.id);
console.log('p.private', p.private);
console.log('p.privateMethod', p.privateMethod);
继承
原型链继承
属性写在构造函数上,通过new创建实例对象
方法写在原型上,如:Person.Prototype.doSomething()
,但会造成代码冗余
封装原型减少代码冗余
function Person(){}
Person.prototype.doSomething = function(){}
Person.prototype.doAnything = function(){}
// 封装,但重写了原型,导致Person原型对象上的constructor属性丢失
Person.prototype = {
doSomething(){},
doAnything(){}
}
// 定义constructor
// Object.defineProperty(对象,属性名,描述对象)
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false, // 不可遍历
value: Person
})
// 对象上有数据属性:configurable(属性是否可以删除),enumerate(属性是否可以遍历),writable(属性是否可以被修改),value(属性值)
问题
- 所有的实例对象默认取得相同的值——解决:在实例对象添加同名属性遮蔽掉
- 包含引用类型的属性,实例间会共享属性,在一个实例上修改属性会导致所有实例的属性都被修改
盗用构造函数继承
在子类构造函数中使用call()或apply()调用父类构造函数
function parent(){}
function son(){
// 继承父类构造函数的属性
parent.call(this)
}
问题:函数不能重用,不能访问父类原型上定义的方法,因为不是通过new创建子类的
组合继承:原型链+盗用构造函数
使用原型链继承原型上的属性和方法,通过构造函数继承实例属性,既可以让每个方法定义在原型上实现复用,又可以让每个实例都有自己的方法
function parent(){}
parent.prototype.doSomething = function(){}
function son(){
parent.call(this); // 第一次调用父类构造函数
}
son.prototype = new parent(); // 第二次调用父类构造函数
son.prototype.constructor = son;
问题:父类构造函数被调用两次
原型式继承
类似于Object.create(原型对象,属性描述对象)
不需要单独创建构造函数就可以实现继承,父类构造函数只被调用一次
问题:包含引用类型的属性,实例间会共享属性(与原型链继承一样)
寄生式组合继承:组合继承+原型式继承
// 封装继承
function inherit(parent, son){
son.prototype = Object.create(parent.prototype);
son.prototype.constructor = son;
}
function parent(){}
parent.prototype.doSomething = function(){}
function son(){
parent.call(this);
}
inherit(parent, son);
类继承
使用extend实现类继承,使用super()调用父类构造函数
面向对象
三大特性:封装、继承、多态
多态比较不好理解:可以理解为多种状态,同一个方法不同的对象会有不同的状态输出