构造函数&&实例
JavaScript 是一门基于原型的语言,它没有创建类,对象被创建是基于引用的,JavaScript 也是一种动态编程语言,这意味着可以在实例化后轻松地在对象中添加或删除属性。
在 Js 中没有类的概念,在 Js 中所存在的是构造函数代替模拟类的存在,对象由某个构造函数构造出来,我们称之为这个对象是 构造函数的实例, 即使是使用 var 声明的一个对象,我们也可以看到他的
__proto__中的 constructor 也是指向的 Object 。
prototype && __proto__
我们已知函数通过 new 关键字调用就是所谓的构造函数,得到一个实例。在这个构造函数,或者说 每个函数 上面,都会有一个 prototype 属性,这个 prototype 属性 指向了一个对象,暂且不管这个对象是什么,只需要知道 函数 有一个 prototype 属性,这个属性可以通过 点的方式访问到。
在构造函数所构造的实例上,或者说 Js 中的每一个对象上都会有一个
__proto__属性(这个属性虽然可以看到,也可以访问到,但并不建议直接使用 修改 对象的原型;详见)实例的
__proto__也指向了一个对象,实例的__proto__和构造函数的 prototype 指向的是同一个对象,我们称这个对象为 原型对象
例:
function Fn() {
this.name = 'Zyc'
}
var f = new Fn()
console.log(Fn.prototype) // 一个 Object
console.log(f.__proto__) // 一个 Object
console.log('Fn & f', Fn.prototype === f.__proto__) // true
原型是什么
从位置上来说,确实如上所述,那么 究竟什么是所谓的 原型 呢,我们需要再从 构造函数出发:
函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是 上述 实例 f 的原型。 而所谓的原型就是:每一个 JavaScript 对象(null 除外) 在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从 原型 "继承" 属性
constructor
在每个 原型对象 中,都会有一个 constructor 属性,这个属性指向其关联的构造函数
function Person() {
}
var person1 = new Person()
console.log('Person && person1', Person.prototype === person1.__proto__) // true
console.log(Person.prototype.constructor === Person) // true
为什么要将构造函数的方法定义在原型 prototype 上面
其实在构造函数内部直接定义方法也是可以的,问题在于,每个构造函数都可能会调用多次去构建实例,如果把方法定义在 构造函数内部,那么在每次调用的时候,都会重新创建这个函数,伴随着函数的创建,会在内存中开辟一块空间存储这个函数,但是每个实例用到的方法内部代码完全一致,就造成了资源浪费。
如果是将方法定义在函数的 原型 上面,所有的实例都能够访问这同一个方法,不会造成资源浪费,代码更高效
还有一种解决方案,就是在构造函数外部预先定义好一个函数,在构造函数内部引用这个函数,那么所有的实例将拥有同一个函数的地址,也可以避免资源浪费
Ps:一个核心观念就是,构造函数会被调用多次创建实例,每一次调用就会伴随着 函数内部代码的执行,如果内部有函数的声明,将会不断的开辟内存空间,就会资源浪费,如果有方法实现公用一个内存地址,那么就可以避免掉性能浪费的问题
实例 && 原型
当我们在获取一个对象的属性时,会优先在对象内部查找这个属性,当在对象内部没有找到这个属性时,就会去对象的原型上面去找,如果在实例的原型上面也没有找到,就会去原型的原型上面去找。
function Person() {
}
Person.prototype['hobby'] = ['唱', '跳', 'Rap']
var person1 = new Person() // 此时 Person 的实例 person1 中并没有 hobby 这个属性
console.log(person1.hobby); // ['唱', '跳', 'Rap']
person1['hobby'] = ['看书', '学习', '运动'] // 给 person1 实例添加了 hobby 属性
console.log(person1.hobby); // ['唱', '跳', 'Rap']
原型的原型
已知 原型对象 也是一个对象,其实原型对象就是通过 Object 构造函数生成的,也就是说,一个实例的原型对象 是 Object 的实例, 那么继而可以得出 原型对象 的
__proto__和 Object 构造函数 的 prototype 指向同一个地址,这个地址也就是 Object 的 原型
Object.prototype['address'] = 'NJ'
function Person() {
}
Person.prototype['hobby'] = ['唱', '跳', 'Rap']
var person1 = new Person()
console.log(person1.hobby); // ['唱', '跳', 'Rap']
person1['hobby'] = ['看书', '学习', '运动']
console.log('hobby:', person1.hobby); // ['唱', '跳', 'Rap']
// 在 Person 的 原型 -> 原型 上定义的 address
console.log('address:', person1.address); // NJ
// 未在任何地方定义的属性:
console.log('age:', person1.age); // undefined
引上述:对象属性的查找规则,我们 既没有在 Person 的 prototype 上定义 address 也没有在 实例内部定义 address,而是在 Object 的 prototype 上定义的 address,在打印
person1.address时的确打印了出来数据。如果未在任何地方定义的数据 最终获取 为 undefined证:对象查找属性规则 = 对象内部 > 原型 > 原型 > 原型 > undefined
原型链
截止到上面,应该可以体会到一个如同链条的存在,所谓的原型链,就是 原型 => 原型 => 原型 原型串联起来的链条,而在一个对象查找一个属性时,就会在原型链上逐步往上查找,要么找到,要么没找到也就是 undefined
Ps:
function Person() {
}
var person2 = new Person()
console.log(person2 instanceof Person); // true
console.log(person2 instanceof Object); // true
如上所述:JavaScript 判断一个对象是否 instanceof 某个函数的依据,即对象 person2 的 原型链 上有没有一个
__proto__是这个函数的 prototype, 如果有,那么 person 就是这个函数的 instance。由于一般所有的原型链最终都会指向顶端的 Object.prototype,所以它们都是 Object 的 instance。一句话理解 instanceof 的运算规则为: instanceof 检测左侧的
__proto__原型链上,是否存在右侧的 prototype 原型。
原型链的终点
首先 原型链 必须也然有终点,如果没有终点在查找对象的属性时会无限查找下去 其次 原型链 的终点并不是 Object.prototype; 亦可以换种说法,原型链的终点就是 Object.prototype 而这个 Object.prototype 是一个特殊的 Object 它特殊在 它的
__proto__指向 null。而我个人更偏向于考虑原型链 的终点为 null。首先需要明确一点,原型链是指 对象 的原型链,所以原型链上的所有节点都是 对象, 不能是字符串、数字、布尔等原始类型。 另外规范要求原型链必须是 有限长度 的(从任一节点出发,经过有限的步骤后必须到达一个终点,显然也不能有环) 那么应该用什么对象作为终点呢? 很显然应该用一个特殊的对象。 Object.prototype 确实是个特殊的对象,我们先假设用它做终点,那么考虑一下,当取它的原型时应该怎么办? 即:
Object.prototype.__proto__应该返回什么? 当然 JavaScript 已经给了我们答案,先不考虑已知的答案 取一个对象的属性时,可能发生三种情况:
- 如果属性值存在,那么返回属性的值
- 如果属性不存在,那么返回 undefined
- 不管属性存在与否,有可能抛出异常
综上所述, 我们已经假设 Object.prototype 是终点了,哪里还能获取属性,所以排除掉 1 。另外抛出异常也不可取,3 排除。 情况 2,它不存在 原型属性了,返回 undefined 呢? 也不好,因为 undefined 一种解释是原型不存在,但是也相当于原型就是 undefined。这样,在原型链上就会存在一个非对象的值。
所以,最佳选择就是 null。一方面,你没法访问 null 的属性,所以起到了终止原型链的作用;另一方面,null 在某种意义上也是一种对象,即空对象(JavaScript 中之所以存在 null, 就是为了表示空对象的)。这样一来,就不会违反 “原型链 上只有对象”的约定。
所以,“原型链的终点是 null” 虽然不是必须不可的,但确实最合理的。
Function
在 JavaScript 里任何东西都是对象,包括函数,可以称为函数对象。所以 Foo 也是对象,既然是对象,必然有
__proto__属性,那么函数对象 Foo 的__proto__又指向了哪里,又是谁的 instance ?JavaScript 里定义了一个特殊的函数叫 Function,可以称作所有函数的爸爸,所有的函数都是它的实例,因此你可以认为,定义 Foo 的时候发生了这样的事情:
var Foo = new Function(args, function_body);
于是:
function Foo() {
}
console.log('Function && Foo:', Function.prototype === Foo.__proto__); // true
console.log(Foo instanceof Function); // true
函数 Foo 由特殊的构造函数 Function 构建,所以 Function.prototype 与 Foo.
__proto__指向同一个对象,也就是函数的 原型注意这里的 Function.prototype,这也是 JavaScript 里一个特殊的对象,Chrome 的 console 里要是输入 Function.prototype,根本什么也打印不出来,什么 native code,就是说它是 内部实现 的。
走到这一步,Function.prototype 并没有走到头,我们继续探索 Function.prototype.
__proto__, 可以发现 ··TMD·· 竟然是 Object.prototype,于是:
Object.prototype['fooName'] = 'HAHAHA'
function Foo() {
}
console.log('Function 的原型的原型:', Function.prototype.__proto__ === Object.prototype); // true
console.log(Foo.fooName); // HAHAHA
那么问题来了,Function 自己呢? 它其实也是个函数,也是个对象,它的
__proto__指向谁? 答案是它自己的 prototype于是:
function Foo() {
}
console.log(Function.__proto__ === Function.prototype); // true
So:所有的函数都是 Function 的 instance, Function自己也是它自己的实例,不过后者严格来说并不准确,Function 并不是它自己创造自己的,而应该看作 JavaScript 里原生的一个函数对象,只不过它的
__proto__指向了它自己的 prototype 而已。
Function && Object
我们已知一般任何对象都是 Object 的 instance, 因为原型链的顶端都指向了 Object.prototype。 那么 Object 本身是什么? Object 也是个函数,而任何函数都是 Function 的实例对象,比如 Array, String,当然 Object 也包括在内,它也是 Function 的实例,于是:
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
WDNMD ??
结合上文中所知的解读上述代码:
已知 Object 是所有对象的构造函数,正因为它是一个函数,所以它是 Function 的实例,故 Object instanceof Function
已知 Function.
__proto__指向 Function.prototype, 指向同一个 原型 这个原型是一个对象且并不是终点,继续获取 Function.prototype.__proto__指向了 Object.prototype, 故 Function 的原型链的顶端亦是 Object.prototype,而 instance 的查找规则就是原型链上是否有一个 原型 是某个构造函数的 prototype, 所以 Function instanceof Object 成立已知 Object.
__proto__=== Function.prototype , Function.prototype.__proto__=== Object.prototype, 故:Object.__proto__=> Function.prototype.__proto__=> Object.prototype 故:Object instance Object 成立已知:Function.
__proto__=== Function.prototype, 故:Function instanceof Function 成立
那么问题来了,Function 和 Object 到底谁先谁后,谁主谁次?于是乎,就有了一个 JavaScript 里经常说到的蛋鸡问题:
Object instanceof Function === true > Function instanceof Object === true
借鉴网上的理解:
首先没鸡没蛋,先有一个特殊对象 root_prototype,它是上帝。
接下来应该是先有 Function,并且定义它的 prototype 和proto,都连上了 root_prototype。
然后才有了 Object,它是 Function 的 instance,继承了 Function。这时候 Object 仍然只是个普通的函数。
然后规定 Object.prototype = root_prototype,这时候 Object 才开始显得特殊,成为了原型链的顶端,否则它和其它函数根本没什么区别。
于是所有的东西,包括 Function,都成了 Object 的 instance 了。
这里要强调 Object 和其它函数的不同之处。Object 之所以特殊,就是因为 Object 的 prototype 被设定为了 root_prototype,仅此而已;而其它函数例如 foo,它的 prototype 只是一个普通的对象,这个对象的proto默认情况下指向 root_prototype。至于为什么这样设定,为什么 Object 会特殊化,大概只是因为 Object 这个名字起得好,而 foo,bar 没那么特殊。所以说白了 Object 函数只是一个盛放 root_prototype 的容器而已,从而使它晋升为一个特殊的函数。
另外值得注意的是,obj instanceof function 并不意味着 obj 就是这个 function 创建出来的,只不过是 obj 的原型链上有 function.prototype 而已。
所以所谓的 Object instanceof Function 和 Function instanceof Object 的蛋鸡问题,前者应该来说是自然而然、不容置疑的,可以认为 Object 函数是 Function 创造出来的;而后者说白了只是因为强行规定了 Object 函数的特殊性,而导致的一个推论,而 Function 并非是 Object 创建的。
一些借鉴来源
讶羽的博客 - JS 深入系列 - 从原型到原型链 - github 地址
JavaScript 原型链以及 Object,Function 之间的关系
为什么原型链的终点是 null,而不是 Object.prototype?
JavaScript 自定义构造函数存在的问题(为什么要使用原型)