原型、原型链、继承
原型、原型链
每个对象拥有一个原型对象。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
参考
- 继承与原型链 - MDN
- 对象原型 - MDN
- Object - MDN
- class constructor - MDN
- 重新认识构造函数、原型和原型链 - 木易杨 博客
- js 从原型链到继承——图解来龙去脉 - 掘金
构造函数
构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。
构造函数和普通函数的区别在于,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。
Object 实例的 __proto__
访问器属性暴露了此对象的 [[Prototype]]
(一个对象或 null)。
方法
- Object.create() - MDN
- Object.getPrototypeOf() - MDN
- Object.setPrototypeOf() - MDN
- new - MDN
- new.target - MDN
- instanceof - MDN
- Object.prototype.isPrototypeOf() - MDN
new
- 创建一个空的简单对象
- 新对象的 [[Prototype]] 指向构造函数的原型对象(prototype)
- 新对象作为this的上下文
- 如果函数没有返回其他对象,那么返回这个新对象
js
function _new() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
const Constructor = [].shift.call(arguments)
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
let obj = Object.create(Constructor.prototype)
// 或者
// let obj = new Object()
// obj.__proto__ = Constructor.prototype
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
let ret = Constructor.apply(obj, arguments)
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj
}
Object.create
TIP
Object.create(null)
可以创建出没有原型&原型链的对象。
js
const _create = function (proto) {
const Fn = function () {}
Fn.prototype = proto
return new Fn()
}
function A() {
this.a = 'test'
}
const a1 = Object.create(A)
const a2 = _create(A)
const a3 = new A()
console.log(Object.getPrototypeOf(a1) === A) // true
console.log(Object.getPrototypeOf(a2) === A) // true
console.log(a1.a) // undefined
console.log(a2.a) // undefined
console.log(a3.a) // test
a1.a = 'a1a'
console.log(a1.a) // a1a
收藏
instanceof
js
function _instanceof(target, Fn) {
if (
(typeof target !== 'object' && typeof target !== 'function') ||
target === null
)
return false
while (target) {
if (target.__proto__ === Fn.prototype) {
return true
} else {
target = target.__proto__
}
}
return false
}
function Test() {
console.log('Test function')
}
const t = new Test()
console.log('Test', _instanceof(t, Test)) // true
console.log('Function', _instanceof(t, Function)) // false
console.log('Object', _instanceof(t, Object)) // true
题目
javascript
function A() {}
A.prototype.n = 1
// A.prototype = {
// n: 1,
// };
const b = new A()
A.prototype = {
n: 2,
m: 3,
}
const c = new A()
console.log('b.__proto__', b.__proto__) // {n: 1}
console.log('c.__proto__', c.__proto__) // {n: 2, m: 3}
console.log('A.prototype', A.prototype) // {n: 2, m: 3}
console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3
A.prototype.n = 44
const d = new A()
console.log('b.__proto__', b.__proto__) // {n: 1}
console.log('c.__proto__', c.__proto__) // {n: 44, m: 3}
console.log('d.__proto__', d.__proto__) // {n: 44, m: 3}
console.log('A.prototype', A.prototype) // {n: 44, m: 3}
console.log(b.n, b.m, c.n, c.m) //1 undefined 44 3
js
function Fn() {
var n = 10
this.m = 20
this.aa = function () {
console.log(this.m)
}
}
Fn.prototype.bb = function () {
console.log(this.n)
}
var f1 = new Fn()
Fn.prototype = {
aa: function () {
console.log(this.m + 10)
},
}
var f2 = new Fn()
console.log(f1.constructor) // ==> function Fn(){...}
console.log(f2.constructor) // ==> Object() { [native code] }
f1.bb() // undefined
f1.aa() // 20
f2.aa() // 20
f2.__proto__.aa() // NaN
f2.bb() // Uncaught TypeError: f2.bb is not a function
TIP
new 返回的是 object 不是 function
javascript
function F() {}
Object.prototype.a = function () {
console.log('a()')
}
Function.prototype.b = function () {
console.log('b()')
}
var f = new F()
console.log(typeof f, typeof F) // object function
console.log('f', f instanceof Function) // false
console.log('F', F instanceof Function) // true
f.a() // a()
// f.b(); // Uncaught TypeError: f.b is not a function
F.a() // a()
F.b() // b()
INFO
js
function Foo() {
Foo.a = function () {
console.log(1)
}
this.a = function () {
console.log(2)
}
}
Foo.prototype.a = function () {
console.log(3)
}
Foo.a = function () {
console.log(4)
}
Foo.a() // 4
let obj = new Foo()
obj.a() // 2
Foo.a() // 1
INFO
javascript
function Foo() {
getName = function () {
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function () {
console.log(3)
}
var getName = function () {
console.log(4)
}
function getName() {
console.log(5)
}
Foo.getName() // 2
getName() // 4
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
// new (Foo.getName)(); // 2
new Foo().getName() // 3
// (new Foo()).getName(); // 3
new new Foo().getName() // 3
javascript
function Foo() {
this.getName = function () {
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function () {
console.log(3)
}
var getName = function () {
console.log(4)
}
function getName() {
console.log(5)
}
Foo.getName() // 2
getName() // 4
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
// new (Foo.getName)(); // 2
new Foo().getName() // 1
// (new Foo()).getName(); // 1
new new Foo().getName() // 1
继承
实现方法:
- 原型链继承
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
- ES6 Class 继承 extends
- 混入式继承多个对象
参考
- JavaScript 常用八种继承方案 - 木易杨 掘金
- JavaScript 深入之继承的多种方式和优缺点 - 冴羽 GitHub
- Javascript如何实现继承? - 前端面试题宝典