__proto__
JavaScript的每一个对象(null除外)都和另一个对象关联,而这里说的另一个对象便是原型。每个对象都从原型继承属性以及方法。
如何确定一个对象关联的另一个对象是哪个呢?
这就涉及到JavaScript对象的一个内部属性[[Prototype]] ,这个属性指向的便是当前对象关联的(或者说继承的)另一个对象。我们是不能访问内部属性的,
我们可以通过 __proto__
来访问,但是最新的web标准已经删除了它,更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf 和Object.setPrototypeOf/Reflect.setPrototypeOf,这两对方法来读写[[Prototype]],
Object.create 可以在创建对象的时候就指定其原型。
每个JavaScript对象的__proto__ 属性指向该对象的原型,一连串的__proto__ 组成了原型链。null是所有原型链的终点。
prototype
这个是函数的原型属性,它指向函数的原型对象
。这个属性有什么用呢?或者说函数的原型对象是干嘛的,
function person(){
console.log(1);
}
函数的调用有四种方法:
- 作为函数
person()
- 作为方法
xxx.person()
- 作为构造函数
new person()
- 通过他们的call()或者apply()方法间接调用
person.call(this);person.apply(this)
我们通过第1、2、4这三种方法调用函数的时候,函数的prototype属性并没有什么用。
当我们 将函数作为构造函数调用的时候。原型对象属性(prototype)就有用了。之前我们说过每个对象都有__proto__
属性或者说内部属性[[Prototype]] ,而函数作为构造函数调用的时候会返回一个对象,那返回的这个对象的__proto__
指向哪里呢?它就指向构造函数person()的prototype属性。
原型对象中包含所有实例共享的属性和方法。原型对象中存在一个属性constructor指向构造函数。
任何JavaScript函数都可以当做构造函数,所以每个JavaScript函数都会有一个prototype属性(Function.bind()创建的函数以及箭头函数除外)。
关于构造函数创建对象。
- 构造函数就是用来构造新对象,所以构造函数调用会创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数初始化这个新创建的对象,并将这个对象用做其调用上下文。因此构造函数可以使用this来引用这个新创建的对象。构造函数看起来像方法调用,但是它会使用这个新对象作为调用上下文,例如
new person()
我们在person函数中使用this它并不指向window,而是指向新创建的对象。- 构造函数通常不使用return关键字,它通常初始化新对象。当构造函数执行完毕时,它会显式返回这个对象。所以说构造函数调用表达式的计算结果就是这个对象。如果构造函数显式使用return返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但是没有返回值,或者返回一个原始值,这时返回值将会被忽略,同时依旧使用创建新对象作为调用的结果。
var name = 'Nick';
function person(){
console.log(this.name);
}
person(); // Nick 此时其上下文为window 也就是this指向了window,所以访问到了Nick
new person(); // 'undefined' // 此时this指向新创建的空对象,而这个空对象没有name属性所以输出了undefind 因为我们在构造函数中没有对这个空对象做任何操作,所以它只有一个属性那就是 `__proto__`指向person.prototype
person.prototype.name = "Tom"
new person(); // Tom 此时通过this沿着继承链找到了prototype上面的name属性所以就输出了 Tom
说到这里我们可以模拟实现下new操作符做的操作。
// 第一个入参为构造函数,其余为构造函数的参数,
function n(){
var obj = new Object(); // 此时obj只有一个属性 __proto__ 指向 Object.prototype
var constructor = Array.prototype.shift.call(arguments); // 从auguments拿到构造函数,同时arguments中删除构造函数,
obj.__proto__ = constructor.prototype; // 将obj的__proto__ 指向构造函数的prototype
// 使用新创建的对象作为构造函数的上下文,调用构造函数
var result = constructor.apply(obj,arguments); // 构造函数初始化这个新对象,
return typeof result === 'object' ? result : obj;
//如果构造函数显式使用return返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但是没有返回值,或者返回一个原始值,这时返回值将会被忽略,同时使用这个新对象作为调用的结果。
}
var temp = n(perosn);
// 等同于
var temp = new person();
// 鉴于__proto__
要被弃用,我们写个新的
function n(){
const obj = new Object(); // 创建一个空对象
const constructor = Array.prototype.shift.call(arguments); // 从参数里面获取构造的函数
obj.constructor = constructor; // 对象的constructor属性指向构造函数
// 将构造函数的原型对象 设置给该对象
Reflect.setPrototypeOf(obj,constructor.prototype);
const result = constructor.apply(obj,arguments);
return typeof result === 'object' ? result : obj;
}
function person(){
this.name = 12;
}
const me = n(person);
console.log(me);
原型链
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。
其他OOP语言中类实例化的时候,会把类中定义的属性方法复制给实例一份,而JavaScript中"类"在实例化的时候,只是给了实例一个__proto__
属性让JavaScript实例对象通过,这个属性找到类定义的属性和方法。从而可以操作和调用“类”的属性和方法。
function Person(){}
var person1 = new Person();
person1.valueOf();
以访问person1的valueOf为例:
- 浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。
- 如果没有,则浏览器检查 person1 对象的原型对象(即person1.__proto__指向的对象也是 Person构造函数的prototype属性所指向的对象)是否具有可用的 valueof() 方法。
- 如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(即person1.__proto__.__proto__指向的对象也就是 Object构造函数的prototype属性所指向的对象)是否具有可用的 valueOf() 方法。这里有这个方法,于是该方法被调用。
- 如果还是没有。则会到Object构造函数的prototype属性所指向的对象的原型对象也就是null,没得找所以就是undefined。