# 原型
JavaScript 是怎么访问对象的方法和属性的?
在 JavaScript 中,是通过遍历原型链的方式,来访问对象的方法和属性。
# prototype原型对象
每个函数 都有一个 默认的prototype属性,其实际上还是一个 对象。
function f11(){
console.log(100)
};
console.log(f11)
// ƒ f11() {
// console.log(100);
// }
console.log(f11.prototype)
// constructor: ƒ f11()
// __proto__: Object
console.log(f11.prototype.constructor)
// f11() {
// console.log(100);
// }
prototype
当创建函数时,JS会为这个函数自动添加 prototype 属性,值是空对象。而一旦把这个函数当作构造函数( constructor )调用(即通过 new 关键字调用),那么JS就会创建该构造函数的实例,实例继承构造函数 prototype的所有属性和方法(实例通过设置自己的 __proto__指向承构造函数的 prototype 来实现这种继承)。
function F(){
};
F.prototype.x=1000;
let f=new F();
f.y=9;
console.log(f.x);//1000
console.log(f.__proto__===F.prototype)//true
console.log(f.__proto__.__proto__===Object.prototype)//true
console.log(f.__proto__.__proto__.__proto__===null)//true
从上面的输出结果看出,f.__proto__指向了其构造函数F的prototype,而F.prototype本身也是一个对象,其内部也有__proto__属性,其指向的是Object.prototype,直到最后Object.prototype指向null,这条原型链才结束。
WARNING
因此,__proto__这个神秘的属性才是原型链形成的真正原因。
原型对象默认有个不可见的constructor,如果要重写prototype,需要特殊处理
......
foo.prototype = {
// constructor: foo, //这种写法是可枚举的,所以最好采用下面的写法
name: "why",
age: 18,
height: 1.88
}
var f1 = new foo()
// 真实开发中可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: foo
})
# 原型链
原型对象本身也是对象,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个就是原型链,JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined。
- Object.prototype是
顶级对象,所有对象都继承自它。它自己的__proto__指向null。 - Function 继承 Function 本身, Function.prototype 继承 Object.prototype 。
- Function.prototype 和 Function.__proto__ 都指向 Function.prototype
- Object.prototype.__proto__ === null ,说明原型链到 Object.prototype 终止。
TIP
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性( null除外)
- 所有的引用类型(数组、对象、函数),都有一个 __proto__ 属性,属性值是一个普通的对象
- 所有的函数,都有一个 prototype 属性,属性值也是一个普通的对象
- 所有的引用类型(数组、对象、函数),__proto__ 属性值指向它的构造函数的prototype 属性值
var obj = {
name: "why",
age: 18
}
// [[get]]操作
// 1.在当前的对象中查找属性
// 2.如果没有找到, 这个时候会去原型链(__proto__)对象上查找
obj.__proto__ = {
}
// 原型链
obj.__proto__.__proto__ = {
}
obj.__proto__.__proto__.__proto__ = {
address: "上海市"
}
console.log(obj.address)
# 图解constructor/prototype/ __proto__
①__proto__和constructor属性是对象所独有的;
② prototype属性是函数所独有的,且函数一创建就有。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性。
第一,__proto__属性,它是对象所独有的,可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(理解为父对象)。
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象里找,如果还没找到,则继续往上找….直到原型链顶端,若还没找到,则返回undefined,由以上这种通过__proto__属性来连接对象直到的一条链即所谓的原型链。
第二,prototype属性:它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。
那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数,从图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数最终都是由Function()构造函数得来,所以constructor属性的终点就是Function()。
# 原型对象总结
- 每个函数都有prototype,constructor和__proto__
- 对象没有prototype,对象有constructor和__proto__
- constructor:从一个对象指向一个函数,指向该对象的构造函数
- prototype:从一个函数指向一个对象,是函数的原型对象
- 实例f的构造函数指向F,而实例
f.__proto__对象==F.prototype,f.__proto__.constructor==F (其实实例和构造函数之间没有联系的!通过原型作为桥梁)
# 静态成员
将绑定在构造函数自身上的属性方法称为静态成员,静态成员可通过构造函数自身访问,而实例无法访问。
同时,当new一个实例后,实例可以直接访问这些构造器属性与原型属性,所以这里将两种属性统称为实例属性,实例属性只有实例才能访问,构造函数自身无法访问
function Fn() {};
Fn.person = '听风是风';
Fn.sayName = function () {
console.log(this.person);
};
Fn.sayName(); // 听风是风
Fn.prototype.sayName=function(){
console.log(921)
}
let people = new Fn();
people.sayName();// 报错,实例无法访问构造函数的静态属性/方法
# 原型遮蔽效应
如果实例自身对某个属性或者方法进行添加,不会更改原型上的方法,同时访问时原型上的方法因为实例已经存在,会自动屏蔽,除非之后使用delete删除后才可以继续访问
function A(){
}
A.prototype.f=100
let a=new A()
console.log(a.f)//100
a.f=1000
console.log(a.f)//1000
delete a.f
console.log(a.f)//100
# 为对象的原型添加新的属性和方法以及原型的动态性
给原型对象动态添加属性时,一般时一个个添加,如果是直接修改prototype,会修改掉对应的constructor
function Fun(){}
let fun0 = new Fun()
Fun.prototype.test=function(){
console.log('test')
}
fun0.test()//test
Fun.prototype.test=function(){
console.log('test1')
}
fun0.test()//test1
function Fun(){}
let fun0 = new Fun()
console.log(fun0.constructor=== Fun) //true
try{
fun0.fun1()
}catch(e){
console.log(e)
//TypeError: fun0.fun1 is not a functionat demo.html:17
}
Fun.prototype.test=function(){
console.log('test')
}
//原型的动态性,可以监听原型的修改了或者添加了什么方法拿来用
fun0.test()//test
Fun.prototype = {
name:'test',
fun1(){
console.log('fun1')
}
}
let fun = new Fun()
console.log(fun instanceof Fun) //true
console.log(fun.constructor === Fun)//false
fun.fun1()//fun1
可以在原型对象中主动声明constructor可避免错误,但是这种恢复constructor会主动将constructor设为可枚举的。需要再通过defineproperty来修改
function Fun(){}
Fun.prototype = {
constructor:Fun,
name:'test',
fun1(){
console.log('fun1')
}
}
let fun = new Fun()
console.log(fun instanceof Fun) //true
console.log(fun.constructor === Fun)//true
fun.fun1()//fun1
for(let i in fun){
console.log(i)
}
//constructor
//name
//fun1
Obejct.defineProperty(Fun.prototype,'constructor',{
enumberable:false,
value:Fun
})
# 分享链接
← 对象 ES5和ES6的对象写法 →