js原型,原型链
文章目录
原型
1. prototype
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。
- Person.prototype ------原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先
- 通过该构造函数产生的对象,可以继承该原型的属性和方法。
- 原型也是对象,也有自己的属性。
// Person.prototype = {} 是祖先
Person.prototype.say= "abc";
function Person(){
}
var person = new Person();
2.constructor查看对象的构造函数
任何一个prototype对象都有一个constructor属性,指向它的构造函数。每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
function Person(){
}
Car.prototype.say = "def";
function Car(){
}
//Car.prototype = {
// constructor : Person
//}
var car = new Car();
console.log(car.constructor);//function Car(){}
//console.log(car.constructor);//function Person(){}
//注释掉的部分:可以更改对象car的构造函数,
// 它本来是function Car(){}实例化的一个对象,
// 现在Car.prototype = {constructor : Person}让它指向构造函数function Person(){}
3. 隐式__proto__属性,查看原型
js在创建对象的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype
new一个构造函数,相当于实例化一个对象,这期间其实进行了这三个步骤:
在构造函数的逻辑最顶端隐式的新建一个this对象,this其实不是不是一个 空对象,
var this = { __proto__ : Person.prototype}
//__proto__
属性 指向的是对象原型。 (每个对象都有__proto__属性,该属性指向一个对象,就是构造函数Person的原型对象(Person.prototype))去调用构造函数Person,从而设置对象的属性和方法并初始化。并返回this对象。(把this返回,这样每一个实例化的对象就有__proto__属性了。)
上面步骤完成后,这个对象就与构造函数Person再无联系,这个时候即使构造函数Person再加任何成员,都不再影响已经实例化了的对象了。(此时该对象有 了自己的属性之后,同时具有了构造函数Person的原型对象的所有成员)
1,__proto__的基本用法
Person.prototype.name = 'abc';
function Person(){
//var this = {
// __proto__ : Person.prototype//指向的是对象原型
// }
}
var obj ={
name : "sunny"
}
var person = new Person();
person.__proto__ = obj;
console.log(person.name);//sunny
// // new一个对象的时候,查找属性name,
// // 先看自己的构造函数里面有没有name属性,如果有就直接用,
// // 如果没有就沿着this里面__proto__ 属性去对象的原型里面查找,这个时候我改变了person.__proto__ = obj;让它指向对象obj,那它自然也改变了。
2 '.'的写法改变原型对象属性的值,那么结果也会跟着改
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
// Person.prototype.name = 'apple';
var person = new Person();
Person.prototype.name = 'apple';
console.log(person.name)//apple
无论Person.prototype.name = 'apple'放在var person = new Person();上面还是下面,person.name的值都是apple,因为它改变的是__proto__ 指向的Person.prototype上的值。值都变了,person.name必然会变化。
3,改变原型让它指向另外的一个空间,原理类似于下面的:
var obj = {name : "a"};
var obj1 = obj;//obj和obj1先指向同一个房间,
obj = {name : "b"};//obj它又指向另外一个房间
console.log(obj1.name);//a
console.log(obj.name);//b
------------------------------------------------------------------------------
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
var person = new Person();
Person.prototype = { //这是把原型给改了,换了一个新对象
name : 'cherry'
}
console.log(person.name)//sunny
分析程序执行的顺序:
首先new一个对象person,调用构造函数,里面隐式的var this = {__proto__ : Person.prototype},让__proto__ 和 Person.prototype指向同一个空间,然后返回this,这个对象就构建完了。
然后Person.prototype = { name : 'cherry' }把自己的空间换了,但是 __proto__ 没有换,__proto__它还是指向原来的Person.prototype的空间值就是sunny。那么查找name属性的时候,去__proto__里面找到Person.prototype.name ,结果就是sunny.。
就像下面的过程:
Person.prototype = {name : "sunny"};
_proto_ = Person.prototype;
Persom.prototype = {name : "cherry"};
console.log(_proto_.name);//sunny
---------------------------------------------------------------
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = {
name : 'cherry'
}
var person = new Person();
console.log(person.name)//cherry
分析:这个就是Person.prototype = {name : 'cherry' }改变了原型对象让它的值为cherry,然后再实例化一个对象的时候再调用都早函数的时候就会查找__proto__的Person.prototype为cherry。
4, 改变原型和改变原型属性的值综合起来:
Person.prototype.name = 'apple';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = { //改变原型
name : 'cherry'
}
Person.prototype.name = 'sunny';//改变原型属性的值
var person = new Person();
console.log(person.name)//sunny
首先是__proto__和 Person.prototype一起指向一个空间值是apple,
然后Person.prototype指向另外一个空间值是cherry,
然后再把第二次Person.prototype指向的空间的内容替换为sunny,最后实例化对象的时候,发现自己没有这个name属性,
根据__proto__ 去原型里面查找,找到的就是第二次指向的空间里里面的值sunny。
每当代码读取某个对象的某个属性的时候,都会执行一次搜索。首先从对象实例本身开始,如果在实例中找到了该属性,则返回该属性的值,如果没有找到,则顺着原型链指针向上,到原型对象中去找,如果如果找到就返回该属性值。
这里要提一点,如果为对象实例添加了一个属性与原型中同名,则该属性会屏蔽掉原型中的同名属性,不会去修改它!使用delete可以删除自己实例中的属性,但是原型中的属性是删除不了的。
原型链
1,构成原型链
//原型链 是一个链条的 形式,可以把对象串联起来
function CreateDog(name,color){
this.name = name;
this.color = color;
}
CreateDog.prototype.say = function(){
console.log(this.color + this.name + '在叫!');
};
var dog = new CreateDog('萨摩耶','白色');
dog.say();
//访问原型的方法
//1.通过构造函数来访问
console.log(CreateDog.prototype);//Object { say: say(), … }
//2. 通过实例化的对象来访问
console.log(dog.__proto__);//Object { say: say(), … }
//js 在创建对象(任何对象,普通对象和函数对象)的时候,都有一个__proto__的属性,
//这个属性用于指向创建他的函数对象的原型对象prototype
console.log(dog.__proto__ === CreateDog.prototype);//true
//同样的,CreateDog.prototype 对象也有一个__proto__ 指向创建他的函数的原型对象 (object)的prototype
console.log(CreateDog.prototype.__proto__ === Object.prototype);//true
//Object.prototype 也有一个__proto__ 指向null
console.log(Object.prototype.__proto__ === null);//true
//原型链 特点是:__proto__ 属性,
2,Object.prototype是原型链的终端。
字面量创建对象的方法其实和new Object()的方法是一模一样的。
var obj = {};
var obj1 = new Object();
它们是相等的。
obj.__proto__ ---->Object.prototype
obj1.__proto__ ---->Object.prototype
Person.prototype = {} ------->Object.prototype
function Person(){
}
函数的原型就是一个字面量的形式,所以原型链的终端就是Object.prototype
3 , 利用Object.create(原型)创建一个对象
//var obj = Object.create(原型)
Person.prototype.name = "sunny";
function Person() {
}
var person = Object.create(Person.prototype);
console.log(person.name);//sunny
4 , 绝大多数对象的最终都会继承于Object.prototype,但是也有不继承的情况
Object.create(原型)里面的"原型"必须是一个Object对象或者空值。(放原始值会报错)
var obj = Object.create(null);
var obj1 = Object.create(123);//报错
现在创建一个对象,把null放进去,发现这个对象什么属性都没有了,没原型了。调用toString()不行,它自己没有toString方法,它也没原型连__proto__
属性都没有,所以根本找不到toString方法,就会报错。
那现在人为的给它加上__proto__
属性,再去看它不会有继承特性,人为的加上,系统不认。
所以说是绝大多数对象继承于Object.prototype,不是所有的对象,因为现在的这个Object.create(null)创建的对象根本没有继承属性,连原型都没有。
5, 关于toString()方法
1,
undefined和null不能调用tostring方法。
var num = 123;num.toString()可以调用tostring()方法,因为数字可以通过包装类来一层层访问,包装类肯定是对象,然后对象的原型链的终端是Object,它有tostring方法。
但是undefined和null也不是对象,也没有包装类,他就是原始值,没有原型,不可能可以调用tostring()方法。
2,数字num调用toString()方法的原理:
var num = 123;
//num.toString();------->new Number(num).toString();
//Number.prototype.toString = function() {}
//Number.prototype.__proto__ = Object.prototype
//Object.prototype.toString = function () {}
首先Object.prototype上有一个toString方法,每一个继承Object.prototype的都可以调用,但是他们自己也重写了这个方法,就是Number.prototype.toString = function() {},每次调用toString()就是调用的自己重写的toString()方法,方便打印出自己想要的结果。
例如
但是如果不重写自己 的方法那么打印出来的就是下面这样的形式(call就是说让它调用顶端的方法)
不仅是Number重写了toString,还有其他的
3,document.write()本质上是调用tostirng方法
var obj = Object.create(null);
document.write(obj);//会报错
document.write(obj.toString());//会报错,因为它没有原型,更不会有toString()方法
-------------------------------------------------------
//现在人为的加上toString()方法
var obj = Object.create(null);
obj.toString = function() {
return '访问访问';
}
document.write(obj);//访问访问
document.write(obj.toString());//访问访问
//证明document.write()本质上是调用tostirng方法
6, 附上经典的原型链图解
继承实现方式:
为了实现继承,__proto__属性会指向上一层的原型对象,而上一层的结构依然类似,那么就利用__proto__一直指向Object的原型对象上!
Object.prototype.proto= null;表示到达最顶端。如此形成了原型链继承。
大致总结一下就是:
1、Object是作为众多new出来的实例的基类 function Object(){ [ native code ] }
2、Function是作为众多function出来的函数的基类 function Function(){ [ native code ] }
3、构造函数的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype
4、原型对象的__proto__都指向Object.prototype
5、Object.prototype.__proto__指向null