先附上神图!
你是不是经常被问到下面这几个问题?
- 创建对象的有几种方法?
- 什么是原型?
- 什么是构造函数?
- 什么是实例?
- 什么是原型链?
instanceof的原理是什么?new运算符的原理是什么?- 如何实现继承?
刚开始你可能前几个问题还回答的有滋有味,不就是个原型链么,早都熟背于心,倒背如流了。可越来越发现不是这么回事了...
下面我们就来一一解答
1. 创建对象的有几种方法?
一般由三种,分别是:
方法一:使用字面量方式
var obj1 = {name: 'obj'} // 会默认new,和下面obj2的方法效果一样
//或
var obj2 = new Object({name: 'obj'})
输出结果如下:
// {name: "obj"}
方法二:使用显示构造函数创建对象
var Fn = function() {
this.name='obj'
}
var obj3 = new Fn()
输出结果如下:
// Fn {name: "obj"}
方法三:Object.create
var P = {name: 'obj'}
var obj4 = Object.create(P)
输出结果如下:
// {}
等一下!!!竟然发现obj4和上边几个方法打印的结果竟然不一样,竟然是个空对象。是因为什么原因呢?
下面就来揭开他的神秘面纱...
首先,在《JavaScript权威指南》这本书上写到,使用Object.create()可以创建一个新对象,其中第一个参数是这个对象的原型。使用它的方法很简单,只需传入所需的原型对象即可,如下:
var o1 = Object.create({x: 1, y: 2}) // o1继承了属性x和y
因此o1是继承了{x: 1, y: 2},打印o1结果为{}。
同理,上例中obj4也就是继承了P。 因此obj4打印结果为{},而P则在其对应的原型链上,我们通过原型链向上查找
obj4.__proto__ // 输出 {name: "obj"}
可以看出P在其原型链上。
如果想创建一个普通的空对象(比如通过{}或new Object()创建的对象),需要传入Object.prototype:
var o3 = Object.create(Object.prototype) // o3和{}和new Object()一样
第一题到这儿就结束了,是不是有一群小伙伴就倒在了Object.create()这里...
2. 什么是原型?
函数有原型,函数有一个属性叫prototype,函数的这个原型指向一个对象,这个对象叫原型对象。这个原型对象有一个constructor属性,指向这个函数本身。关系如下图所示:
3. 什么是构造函数?
凡是通过new来操作函数function,这个function就是个构造函数,如方法二中的Fn。构造函数也是函数,任何函数只要用new进行操作,函数本身都可以当做构造函数。
函数都有prototype属性,声明一个函数的时候,js引擎会给函数自动加上prototype属性。
原型对象如何被区分被哪个构造函数引用:constructor(构造器)
Fn.prototype.constructor === Fn // true
4. 什么是实例?
只要是个对象,就是个实例。如:obj1、obj2、obj3都是实例。
5. 什么是原型链?
Object.prototype是整个原型链的顶端,从那张经典原型链的图可以看出来。原型链是通过prototype和__proto__来完成原型链的向上查找。
原型对象和原型链之间的关系和作用:如果构造函数中增加了很多属性和方法,那么它的实例就可以共用这些属性和方法,当有多个实例的时候,想共用这些实例和方法的时候,此时原型对象就上场了,可以将属性和方法存到原型对象(Fn.prototype)上。
通过在原型对象上增加属性和方法以后,通过原型链的方式,实例对象是可以找到原型对象上增加属性和方法,实例也是可以拥有的。
任何一个实例对象,通过原型链,找到它上面的原型对象上面的属性和方法,这些属性和方法都是被实例所共享的。如下:
var Fn = function() {
this.name='obj'
}
var obj3 = new Fn()
Fn.prototype.run = function() {
console.log('run')
}
var obj5 = new Fn()
obj3.run() // run
obj5.run() // run
// 两个实例上有相同的方法
js引擎的分析方式: 在访问一个实例的时候,先看实例本身上有什么方法,当在这个实例本身没有找到相关方法或属性的时候,就会通过__proto__向原型对向上查找,通过__proto__逐层向上查找,直到Object.prototype,如果依旧未找到,就返回该方法或属性未被定义。
- 构造函数(函数)才会有
prototype,对象是没有prototype的 - 只有
new出来的实例有__proto__。函数既是函数也是对象,因此也有__proto__。
// 这几个关系非常重要,参考神图
Function.__proto__ === Function.prototype // true
Object.prototype.constructor === Object // true
Object.__proto__ === Function.prorotype // true
Function === Function.prorotype.constructor // true
可以理解为:Fn的构造函数式Function。Fn这个普通的函数是Function这个构造函数new出来的一个实例,也就是经典图中的最外层的连线的示例!!!
6. instanceof的原理是什么?
在解释其原理前,我们先知道其作用是什么,参考:instanceof。
作用:instanceof运算符用于检测构造函数的prototype属性(constructor.prototype)是否出现在某个实例对象(参数object)的原型链上。
判断实例对象是否为构造函数的实例的时候,判断的是实例对象的__proto__属性和构造函数的prototype属性指向的是否为同一个地址。只要是在个原型链上,instanceof返回的都为true
obj3 instanceof Fn // true
obj3 instanceof Object //true
原理
obj3.__proto__ === M.prototype // true
M.prototype.__proto__ === Object.prototype // true
因此
obj3 instanceof Object //true
因此obj3不一定是Object的一个实例。尤其是A继承了B,B继承了C的时候,此时,A生成的实例对象,用instanceof判断A、B和C都返回true,此时就无法判断是谁的实例。此时就需要用constructor属性来判断了。
obj3.__proto__.constructor === M // true
obj3.__proto__.constructor === Object // false
因此,在判断实例对象是否为构造函数的实例的时候,用constructor判断比用instanceof更加严谨!
7. new运算符的原理是什么?
new运算符的操作过程如下:
- 一个新对象被创建。它继承自
Fn.prototype - 构造函数
Fn被执行。执行的时候,相应的参数会被传入,同时,上下文(this)会被指定为这个新实例。new Fn等同于new Fn(),但只能用在不传递任何参数的情况下。 - 如果构造函数返回了一个“对象”,那么这个对象会取代整个
new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象.
实现new运算符的效果
var new2 = function(func) {
// 生成新对象,并指定构造函数的新对象
// 新创建的对象继承自func构造函数的原型对象
var o = Object.create(func.prototype)
// 执行构造函数,并转移上下文
var k = func.call(o)
// 判断是否为对象
if (typeof k === 'object') {
return k
} else {
reutrn o
}
}
验证
var obj6 = new2(Fn)
obj6 instanceof Fn // true
obj6 instanceof Object // true
obj6.__proto__.constructor === Fn // true
// 在原型链上增加方法
Fn.prototype.walk = function() {
console.log('walk')
}
obj6.walk() // walk
obj3.walk() // walk
new运算符带参数
function newOperator(ctor, ...args) {
if (typeof ctor !== 'function) {
throw new TypeError('Type Error')
}
const obj = Object.create(ctor.prototype)
const res = ctor.apply(obj, args)
const isObject = typeof res === 'object' && res !== null
const isFunction = typeof res = 'function'
return isObject || isFunction ? res : obj
}
8. 如何实现继承?
function Parent() {
this.name = 'parent'
this.play = [1,2,3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
var s = new Child()
console.log(s instanceof Child, s instanceof Parent)
console.log(s.constructor)
如果再有人问你上面的问题,就让问你的人面对疾风吧...
延伸:
__proto__特性:
- 对象特有(
function也是对象) __proto__指向上层(创建自己的那个构造函数)的prototype- 对象可以从
prototype中继承属性和方法
prototype特性:
prototype是函数特有的prototype用于存储共享的属性和方法
constructor特性:
- 函数特有,定义在
prototype里 - 通过
new创建实例时,该实例便继承了prototype的属性和方法
Object:既是对象,也是构造函数
- 作为对象:
Object.__proto__ === Function.prototype - 作为函数:
Object.prototype是原型链的顶端,Object.prototype.__ptoto__ = null
Function:既是对象,也是构造函数
- 作为对象:
Function.__protp__ === Function.prototype - 作为函数:
Function.prototype用于共享,而Function.prototype.__proto__ === Object.prototype
Array(Date 等):既是对象,也是构造函数
- 作为对象:
Array.__proto__ === Function.prototype - 作为函数:
Array.prototype用于共享,Array.prototype.__proto__ === Object.prototype
对象Parent:既是对象,也是构造函数
- 作为对象:
Parent.__proto__ === Function.prototype - 作为函数:
Parent.prototype.__proto__ === Object.prototype
附:
- 原型链顶端是
Object.prototype - 构造函数创建的对(
Object、Function、Array、普通对象等)都是Function的实例,他们的__proto__均指向Function.prototype - 除了
Object,所有对象(构造函数)的prototype,均继承自Object.prototype
// 需要理清楚是由谁创造出来的
Object.prototype.a = 'a'
Function.prototype.a = 'a1'
function Person() {} // Person是由Function.prototype创造出来的,Person.__proto__ === Function.prototype
console.log(Person.a) // 'a1'
var p = new Person()
console.log(p.a) // 'a'
p.__proto__ === Person.prototype // 在Person.prototype没有a,继续往上查找
p.__proto__.__proto__ === Person.prototype.__proto__
p.__proto__.__proto__ === Object.protoype // 在Object.protoype上有a
p.__proto__.__proto__.constructor.constructor.constructor
p.__proto__.__proto__.constructor // Object() { [native code] }
此时Object由谁创造
Function.prototype创造了Object,Object上没有constructor就会找Function.prototype的constructor
Object.constructor // Function() { [native code] }
因此 Object.constructor 到了 Function,再往上找constructor时,Function上没有constructor,就会找到Function.prototype上的constructor,就又回到了Function来,因此不管之后又多少个.constructor,永远返回Function!!!
有4个核心点:
Object是由Function.prototype创造的Function.prototype.__proto__ === Object.prototypeFunction自己创造自己,非常重要,即Function.__proto__ === Function.prototype。函数也是被Function创建的,那么函数的__ptoto__也应该指向Function的原型(Function.prototype),这是一个环形结构,函数Function的prototype和__proto__属性指向同一个对象,Fucntion.prototype和Object.protoype是两个特殊的对象,他么由js引擎来创造。js中万物皆对象
Object.prototype.a = 'a'
function Person() {}
console.log(Person.a) // a
native code 是平台原生代码
Function自己创造自己
Function.prototype === Function.prototype
Function.prototype.constructor === Function
相关题目:
//1. 写出打印结果,并说出原因
var A = function(){}
A.prototype.n = 1;
var b = new A(); // b的__proto__指向n的地址
A.prototype = { // 把A的prototype指针改变
n: 2,
m: 3
}
var c = new A();
console.log(b.n, b.m, c.n, c.m) // b.n,b.m 、 c.n,c.m 都是通过__proto__向上进行查找的
// 1, undefined,2, 3
// 2. 看原型链
Object.prototype.a = 'a'
Function.prototype.a = 'a1'
function Person() {}
console.log(Person.a) // a1
var xiaohong = new Person()
console.log(xiaohong.a) // a
xiaohong.__proto__ === Person.prototype
Person.__proto__ === Object.prototype
Object.prototype.a = 'a'
Person.__proto__ === Function.prototype
xiaohong.__proto__.__proto__.constructor.constructor.constructor.constructor // Function() { [native code] } function是由自己创建的自己
xiaohong.__proto__.__proto__ === Object.prototype
Object.prototype.a = 'a'
function Person() {}
console.log(Person.a) // a
为什么Object.prototype和Function.prototype差别这么大? Function.proto === Function.prototype // true