构造函数、实例、原型对象的三角关系
function Parent(){
}
Parent.prototype.num = 1
const son = new Parent()
定义
- Parent 函数是构造函数;
- son 是构造函数Parent的实例;
- Parent.prototype 是原型对象;
指向
- 实例的隐式原型指向构造函数的原型对象:
son.__proto__ === Parent.prototype - 构造函数的显式原型也指向原型对象:
Parent.prototype - 原型对象的构造器指向构造函数:
Parent.prototype.constructor === Parent
拓展
- 所有构造函数的__proto__指向Function.prototype
- Function.prototype的__proto__ 指向 Object.prototype
- Object.prototype的__proto__ 指向null
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
__proto__来自与Object.prototype,当使用obj.__proto__时可以理解为Object.getPrototypeOf(obj),获取obj的原型对象。
继承与原型链
定义
继承只有一种数据类型(对象),每一个实例对象都有一个隐式属性(proto)指向它的构造函数的原型对象(prototype)。
实例顺着__proto__一直向上查找的直到(null)过程中串起来一个链条,称之为原型链。
function Parent(){
this.a = 1;
this.b = 2;
}
let son = new Parent();
Parent.prototype.b =3;
Parent.prototype.c =4;
以上的原型链就是:
{a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(son.a) // 1
console.log(son.b) // 2
console.log(son.c) // 4 去原型链上查找
// 此时如果删除o.b属性,就会向上查找`f.prototype.b`
delete son.b
console.log(son.b) // 3
原型检测
- Object.getPrototypeOf(obj) 返回指向对象的原型对象
- instantof 运算符,检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
Function.prototype === Object.getPrototypeOf(Parent) // true
Object.prototype === Object.getPrototypeOf({}) // true
Parent.prototype === Object.getPrototypeOf(son) // true
son instanceof Object // true
son instanceof Parent // true
巩固(经典问题)
一下习题来自 JS面试题-原型链相关,感谢!
1. 入参的影响
function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a); // 1
console.log(new B().a); // undefined
console.log(new C(2).a); // 2
分析:new B().a 没有入参,this.a=undefined
2. 各自的原型链
var F = function () {
}
Object.prototype.a = function () {
console.log('a')
}
Function.prototype.b = function () {
console.log('b')
}
var f = new F()
console.log(F.a()) // a
console.log(F.b()) //
console.log(f.a())
console.log(f.b()) // f.b is not a function
分析:
-
F.__proto__ => Function.prototype,Function.prototype.__proto__ => Object.prototypeF可以找到a、b方法; -
f.__proto__ => F.prototype,F.prototype.__proto__=>Object.prototypef函数没有b方法,报错
3. 原型对象属性修改对实例的影响
var A = function () {
}
A.prototype.n = 1
var b = new A()
A.prototype = {
n: 2,
m: 3
}
var c = new A()
A.prototype = {
// 这种写法会清空之前的属性
}
var d = new A()
console.log(b.n, b.m, c.n, c.m, d.n, d.m)
分析:原型对象的属性修改,不改变已经实例化的对象,对之后的生成新的实例有影响。
b.m 、d.n、d.m 输出undefined
4. 原型和构造函数的属性互不干涉
function Fn () {
this.x = 100
this.y = 200
this.getX = function () {
console.log(this.x)
}
}
Fn.prototype.getX = function () {
console.log(this.x)
}
Fn.prototype.getY = function () {
console.log(this.y)
}
var f1 = new Fn
var f2 = new Fn
// 输出
console.log(f1.getX === f2.getX) // false
//f1.getX、f2.getX 返回值是实例自身具有的函数,两个函数之间没有相等关系
console.log(f1.getY === f2.getY) // true
//f1.getY、f2.getY 同时指向原型链Fn.prototype.getY函数
console.log(f1.__proto__.getY === Fn.prototype.getY) // true
// 没有疑问,f1.__proto__ === Fn.prototype
console.log(f1.__proto__.getX === f2.getX) // false
// 又是在比较两个函数,false
console.log(f1.__proto__.getX === Fn.prototype.getX) // true
// 同一个函数
console.log(f1.constructor) // Fn
console.log(Fn.prototype.__proto__.constructor) // Object
// Fn.prototype.__proto__.constrnctor => Fn.prototype的构造函数是Object
f1.getX() // 100
f1.__proto__.getX() // undefined
f2.getY() // 200
Fn.prototype.getY() // undefined
// Fn.prototype 和 Fn中的this都是指向自己,Fn.prototype取不到Fn中的属性的。
5. this 指向问题(有趣)
function fun () {
this.a = 0
this.b = function () {
console.log(this.a)
}
}
fun.prototype = {
b: function () {
this.a = 20
console.log(this.a)
},
c: function () {
this.a = 30
console.log(this.a)
}
}
var my_fun = new fun()
my_fun.b() // 0
my_fun.a // 0
my_fun.c() // 30 !import
my_fun.a // 30
var my_fun2 = new fun()
console.log(my_fun2.a) // 0
my_fun2.__proto__.c() // 30 !import
console.log(my_fun2.a) // 0
console.log(my_fun2.__proto__.a) // 30
分析:
my_fun.c() 执行完毕,this.a = 30将修改 my_fun 函数的a属性为30 my_fun = {a:30;...}
my_fun2.__proto__.c()执行完毕,this.a =30 给fun.prototype函数添加了一个a=30的属性。