了解原型、原型链前需要先了解构造函数,new操作符
构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
function Foo () {
this.name = 'gl'
this.fun = function () { console.log('eat')}
}
const Foo = function (){
this.xxx = xxx;
}
const fooInstancing = new Foo();
const fooInstancing1 = new Foo();
构造函数的三大特点:
- 构造函数的函数名的第一个字母通常大写。
- 函数体内使用this关键字,代表所要生成的对象实例。
- 生成对象的时候,必须使用new命令来调用构造函数。
new操作符
new命令的作用,就是执行一个构造函数,并且返回一个对象实例。使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将空对象的原型指向了构造函数的prototype属性。
- 将空对象赋值给构造函数内部的this关键字。
- 开始执行构造函数内部的代码。
也就是说,构造函数内部,this指向的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。
后续了解了原型之后我们再简单实现一个new
原型
在JavaScript没有class的时候,是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个prototype
属性,它的属性值是一个对象(prototype),这个对象包含了可以由该构造函数的所有实例共享的属性和方法
。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针
,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针(proto)被称为对象的原型。
一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来访问这个属性,但是最好不要使用这个属性,因为它快芭比Q
了。ES5 中新增了一个Object.getPrototypeOf()
方法,可以通过这个方法来获取对象的原型。
结论
以上明白了 两点
- prototype(原型,类型是对象,一般叫原型对象)是构造函数的
- proto、Object.getPrototypeOf(obj)是获取对象的原型
- prototype能干啥,共享~可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
// 构造函数本质是函数
function DoSomething(){}
console.log( DoSomething.prototype );
// 两种写法
var DoSomething = function(){};
console.log( DoSomething.prototype );
// ?
{constructor: ƒ}
constructor: ƒ DoSomething()
arguments: null
caller: null
length: 0
name: "DoSomething"
prototype: {constructor: ƒ}
[[FunctionLocation]]: VM1158:1
[[Prototype]]: ƒ ()
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: (...)
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示。
function DoSomething(){}
DoSomething.prototype.foo = "bar";
console.log( DoSomething.prototype ); // ?
然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例。然后,就可以在这个对象上面添加一些属性。
function DoSomething(){}
DoSomething.prototype.foo = "bar";
var doSomeInstancing = new DoSomething();
doSomeInstancing.prop = "some value";
console.log( doSomeInstancing ); // ?
__proto__哪去了?
这时候会发现我们的__proto__哪去了,对象实例里面咋没有,凡事先看权威文档
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
proto 的读取器 (getter) 暴露了一个对象的内部 [[Prototype]]
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#%E7%BB%A7%E6%89%BF%E5%B1%9E%E6%80%A7
备注: 遵循 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过Object.getPrototypeOf() 和 Object.setPrototypeOf()
访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 proto。但它不应该与构造函数 func 的 prototype 属性相混淆。被构造函数创建的实例对象的 [[Prototype]] 指向 func 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。
那我们在试试Object.getPrototypeOf()和__proto__
的关系
Object.getPrototypeOf(doSomeInstancing);
// ?
doSomeInstancing.__proto__;
// ? true or false
Object.getPrototypeOf(doSomeInstancing) === doSomeInstancing.__proto__;
某些浏览器内部还是支持__proto__的,例如谷歌,结论[[Prototype]] 就是 proto;
下文还是用 大家熟知的__proto__说道
- 回过头发现doSomeInstancing.__proto__和doSomething.prototype打印结果是一样的
Object.getPrototypeOf(doSomeInstancing) === DoSomething.prototype;
// ?
所以 Object.getPrototypeOf(doSomeInstancing) === doSomeInstancing.proto === DoSomething.prototype
那什么是原型链呢
思考一个问题,如果访问doSomeInstancing里的一个属性,浏览器是怎么做的?
- 浏览器首先会查看
doSomeInstancing
中是否存在这个属性 - 如果
doSomeInstancing
不包含属性信息,那么浏览器会在doSomeInstancing
的__proto__
中进行查找 (同doSomething.prototype
). 如属性在doSomeInstancing
的__proto__
中查找到,则使用doSomeInstancing
中__proto__
的属性。 - 否则,如果
doSomeInstancing
中__proto__
不具有该属性,则检查doSomeInstancing
的__proto__
的__proto__
是否具有该属性。默认情况下,任何函数的原型属性__proto__
都是window.Object.prototype.
因此,通过doSomeInstancing
的__proto__
的__proto__
( 同doSomething.prototype
的__proto__
(同Object.prototype
)) 来查找要搜索的属性。 - 如果属性不存在
doSomeInstancing
的__proto__
的__proto__
中,那么就会在doSomeInstancing 的__proto__
的__proto__
的__proto__
中查找。然而,这里存在个问题:doSomeInstancing
的__proto__
的__proto__
的__proto__
其实不存在。因此,只有这样,在__proto__
的整个原型链被查看之后,这里没有更多的__proto__
,浏览器断言该属性不存在,并给出属性值为undefined
的结论。
找一个属性,要顺着原型__proto__
一直找下去,这时候就会形成一个链路,这就是原型链的查找规则,而原型链就是这个规则本身。
但是要注意一点__proto__
只是提供一种查找规则,是非标准的,实际指向的还是prototype 原型对象。实际开发中不推荐使用,推荐在prototype上操作。
constructor = 构造函数
**对象的原型( __ proto __ )和原型对象(prototype)**里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
function DoSomething(eat){
this.eat = eat;
}
var doSomeInstancing = new DoSomething('吃💩');
console.log(DoSomething.prototype.constructor);
console.log(doSomeInstancing.__proto__.constructor);
FQA
proto、prototype、constructor关系
属性查找机制
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是 proto 指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 依此类推一直找到 Object 为止(null)。
- proto 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
原型链的终点是什么?
由于Object是构造函数,原型链终点是Object.prototype.proto,而Object.prototype.proto=== null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.proto。
代码输出
function DoSomething(){
}
DoSomething.prototype.foo = "bar";
var doSomeInstancing = new DoSomething();
doSomeInstancing.prop = "some value";
// 下面代码输出
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop); // some value
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo); // bar
console.log("doSomething.prop: " + DoSomething.prop); // undefined
console.log("doSomething.foo: " + DoSomething.foo); // undefined
console.log("doSomething.prototype.prop: " + DoSomething.prototype.prop); // undefined
console.log("doSomething.prototype.foo: " + DoSomething.prototype.foo); // bar
function Fn(){
var a = 12
this.getName = function(){
console.log('private getName')
}
}
Fn.prototype.getName = function (){
console.log('public getName')
}
var fn = new Fn()
var fn1 = new Fn()
// 1,2
console.log(fn.a) // undefined
console.log(fn.getName()) // private getName
// 3,4,5
console.log(fn.getName === fn1.getName) // false
console.log(fn.__proto__.getName === fn1.__proto__.getName) // true
console.log(fn.__proto__.getName === Fn.prototype.getName) // true
//6,7
console.log(fn.hasOwnProperty === Object.prototype.hasOwnProperty) // ture
console.log(fn.constructor === Fn) // ture
new如何实现
- 创建一个空对象,作为将要返回的对象实例。
- 将空对象的原型指向了构造函数的prototype属性。
- 将空对象赋值给构造函数内部的this关键字。
- 开始执行构造函数内部的代码。
- 返回此对象
function _new (fn, ...args) {
// 不是一个函数
if (typeof fn === 'function') {
///创建一个空对象,作为将要返回的对象实例。
const obj = new Object();
// 将空对象的原型指向了构造函数的prototype属性。
obj.__proto__ = fn.prototype;
// 将空对象赋值给构造函数内部的this关键字
const result = fn.apply(obj, args);
return result instanceof Object ? result : obj;
} else {
throw TypeError('not a function');
}
}
原型的应用
- instanceof 如果A instanceof B。判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。
- vue2的eventBus
- 给某个对象加一个公用封装方法