1,一些基本的概念
- 隐式原型和显示原型
- 所有的实例对象, 都有隐式原型属性__proto__
- 所有的构造函数,都有显示原型属性prototype
- 他们指向同一个对象, 这个对象就是原型对象
- 构造函数.prototype === 实例对象.__proto__
const obj = {}
const arr = []
const fun = function(){}
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype) // true
console.log(fun.__proto__ === Function.prototype) // true
- constructor:原型对象上的constructor属性是指向构造函数
const obj = {}
console.log(obj.__proto__.constructor); // ƒ Object() { [native code] }
function Person() {}
const p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__.constructor === Person) // true;
2,原型链
js原型链,是一种在对象上查找属性和方法的规则。
- 查找属性和方法时,先在自身查找,如果有,返回并结束。
- 如果自身上没有该属性或方法,到其隐式原型(proto)上查找,如果有,返回并结束。
- 如果原型上也没有,则去原型的原型上查找,以此类推,直到原型链的终点。如果都没有找到,返回undefined
- 原型链的终点,就是Object构造函数的prototype。即Object.prototype。它的隐式原型__proto__为null
console.log(Object.prototype.__proto__); // null
- Function函数是所有函数(包括自身)的构造函数, 所有函数的隐式原型, 都和Function的显示原型指向同一个对象
const fun = function(){}
fun.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
- Function的prototype,这个对象的隐式原型又被JS自动指向Object的原型
Function.prototype.__proto__ === Object.prototype // true
- Object.hasOwnProperty可以查看对象自身上是否有某个属性, 而不去原型链上查找
const obj = {}
console.log(obj.toString()); // [object Object]
console.log(obj.hasOwnProperty('toString')); // false
3,原型链总结
- 原型就是JS查找属性的一套规则
- 所有的实例对象, 都有隐式原型属性__proto__, 所有的构造函数有显示原型属性prototype, 他们指向同一个对象, 这个对象就是原型对象
- 原型对象上又有constructor属性指回构造函数
- 在查找对象上的属性的时候, 会先在自身查找, 如果没有就到原型上查找, 还没有就到原型的原型上查找, 直到找到Object的原型对象上, 如果仍然没有, 就返回undefined
- 原型链的终点就是Object的原型对象, 它没有的隐式原型指向了null
- Function函数是所有函数(包括自身)的构造函数, 所有函数的隐式原型, 都和Function的显示原型指向同一个对象, 这个对象的隐式原型, 又被JS自动指向Object的原型
- Object.hasOwnProperty可以查看对象自身上是否有某个属性, 而不去原型链上查找
4,关于__proto__的补充
- __proto__现在已经不被推荐使用,在文档中使用[[Prototype]]对其进行描述
- 如果要读取__proto__,推荐使用Object.getPrototypeOf(obj) 返回对象 obj 的 [[Prototype]]
function Parent(name, age){
this.name = name
this.age = age
}
const p = new Parent('zhangsan', 38)
console.log(p.__proto__ === Object.getPrototypeOf(p)); // true
console.log(Object.getPrototypeOf(p) === Parent.prototype) // true
- Object.setPrototypeOf(obj, proto) —— 将对象 obj 的 [[Prototype]] 设置为 proto。
function Parent(name, age){
this.name = name
this.age = age
}
const p = new Parent('zhangsan', 38)
Object.setPrototypeOf(p, {
say(){
console.log('say')
}
})
p.say() // say
console.log(Object.getPrototypeOf(p) === Parent.prototype) // false
- Object.create(proto, [descriptors]) —— 利用给定的 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。
const person = {
name: 'zhangsan',
age: 18
}
const p = Object.create(person, {
say: {
value: function(){
console.log('say')
}
},
address: {
value: 'beijing'
}
})
console.log(p) // {address: 'beijing', say: ƒ}
p.say() // say
console.log(p.address); // beijing
console.log(Object.getPrototypeOf(p) === person); // true
5,继承
原型链继承
- 父类的实例作为子类的原型对象
- 易于实现,核心就是 Children.prototype = new Parent()
- 缺点
- 创建子类实例时,不能传参
function Parent(name, age){
this.name = name
this.age = age
}
Parent.prototype.say = function(){
console.log('hello')
}
function Children(){}
Children.prototype = new Parent()
const child = new Children()
child.say() // hello
借用构造函数继承
- 在子类中通过call调用父类,实现传参,解决了传参和共享父类引用属性的问题
- 可以在创建时传参,但是不能使用父类的方法
function Parent(name, age){
this.name = name
this.age = age
}
function Children(name, age){
Parent.call(this, name, age)
}
组合式继承
- 将上面两种方式结合
- 可以传参
- 可以复用方法
function Parent(name, age){
this.name = name
this.age = age
}
function Children(name, age){
Parent.call(this, name, age)
}
Children.prototype = new Parent()
Children.prototype.constructor = Children
组合寄生式继承
- 创建一个用于封装继承过程的函数, 在函数中对父类的实例对象进行增强之后, 再指定为子类的原型
function Parent(name, age){
this.name = name
this.age = age
}
function Children(name, age, address){
Parent.call(this, name, age)
this.address = address
}
function inherit(Children, Parent){
// 创建一个父类的实例对象,将这个实例对象赋值给子类的原型对象
const obj = new Parent()
obj.constructor = Children
Children.prototype = obj
// 对子类的原型对象进行扩展
obj.say = function(){
console.log('say')
}
}
inherit(Children, Parent)
const c = new Children('zhangsan', 18, 'beijing')
console.log(c)
c.say()
- inherit方法还有可优化的地方,就是不必实际创建父类实例,而只要能获取父类原型就可以
function inherit(Children, Parent){
function F(){}
F.prototype = Parent.prototype
const obj = new F()
Children.prototype = obj
obj.constructor = Children
// 对子类的原型对象进行扩展
obj.say = function(){
console.log('say')
}
}
- 还可以再次通过Object.create()进行修改
6,new操作符
new
new操作符用于实例化,他内部做了以下事情:
- 创建一个空的对象newInstance
- 如果构造函数的prototype为一个对象,则将newInstance的__proto__指向构造函数的prototype,否则newInstance将保持为一个普通的对象,__proto__指向Object.prototype
- 使用给定参数执行构造函数,并将newInstance绑定为this上下文
- 如果构造函数返回非原始值,则该返回值成为整个new表达式的结果。否则返回newInstance
我们来模仿它的行为,实现一个自己的new
function MyNew(constructor, ...args){
const newInstance = {} // 创建一个新对象
if (constructor.prototype !== null && typeof constructor.prototype === 'object') {
newInstance.__proto__ = constructor.prototype
}
const result = constructor.apply(obj, args) // 通过apply调用构造函数, 并将参数传递进去,改变this指向
return result instanceof Object ? result : obj // 返回一个对象,如果构造函数返回的是对象,则返回构造函数返回的对象,否则返回新创建的对象
}
new.target
- 函数有两种调用方式:直接调用,作为构造函数被调用(new)
- 当作为构造函数被调用时,new.target指向被new执行的构造函数
- 当直接调用时,new.target为undefined
- 可以通过new.target判断函数的调用方式
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"