# 原型

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()。

# 原型对象总结

  1. 每个函数都有prototype,constructor和__proto__
  2. 对象没有prototype,对象有constructor和__proto__
  3. constructor:从一个对象指向一个函数,指向该对象的构造函数
  4. prototype:从一个函数指向一个对象,是函数的原型对象
  5. 实例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
})

# 分享链接

资料1 (opens new window)

资料2 (opens new window)

最后更新: 2/12/2024, 9:14:18 AM