原型
对象有很多属性和方法。如何批量的生产一类对象,用构造函数。
既然是一类对象,就有很多共同的属性和方法,把这些共同的属性和方法放到一个函数里,当需要创建新的该类对象时,调用这个函数,把这个函数里之前放的共同的属性和方法复制一份给这个新对象,不同的属性和方法通过往这个函数里传递参数加以改变,就成了这个新对象私有的属性和方法。这个函数就叫构造函数,这个对象就是该构造函数的实例对象。
问题是,相同的属性和方法,复制一份放到每一个新创建的对象里,是浪费内存的,这就是构造函数的缺点。
不如在每个新对象里设置一个属性,这个属性的值是一个地址,然后这个地址指向它们共有的属性和方法,而这些共同的属性和方法又可以看做是一个对象,这种创建对象的方法,就是JavaScript原型对象。
__proto__是每个对象都有的一个属性,而prototype是函数才会有的属性, JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。(意思是一个普通的对象只有__proto__属性,而一个函数不仅有prototype属性,还有__proto__属性,这个函数的prototype属性值是一个对象,这个对象就是该构造函数生成实例对象的原型对象,这个对象又有着__proto__属性,指向它自己的原型对象,也就是实例对象的原型对象的原型对象,即Object.prototype)
var 对象=new 函数(){}
对象.__proto__===函数.prototype(实例对象的__proto__属性指向的对象和其构造函数prototype属性指向的对象是同一个对象,这个对象就是它们俩的原型对象) 每个对象在堆内存中都单独的存在,如果一个对象A的某个属性的属性值是另一个对象B,那B并不在A里面,他们处于不同的内存空间,也就是说A的这个属性存储的只是B在内存空间中地址,通过这个地址找到B所在的内存空间,继而获取B里面的属性和方法。
实例对象是对象,占据堆内存空间1,它的构造函数也是对象,占据堆内存空间2,同类实例对象共有的属性和方法被打包成了另一个对象,占据堆内存空间3,当然这个对象不直接存储在构造函数这个对象里,构造函数通过prototype属性指向这个对象(空间3),构造函数的实例对象则通过__proto__属性指向这个对象(也是空间3)。
问:都指向一个原型对象了那要构造函数有什么用?为了给实例对象生成私有的属性。(通过传递给构造函数的参数)
构造函数为什么也要指向这个原型对象?事实上是在创造实例对象时,构造函数的prototype属性存储的原型对象的地址复制了一份传给了实例对象的__proto__属性,所以它们才指向了用一个原型对象。
使用new命令通过构造函数创建实例对象的过程:
1.创建一个空对象,作为将要返回的对象实例。2.将这个空对象的原型,指向构造函数的prototype属性。
3.将这个空对象赋值给函数内部的this关键字。
4.开始执行构造函数内部的代码。
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……继续上面的公式:
对象.__proto__===其构造函数函数.prototype 对象.prototype为undefined(因为普通对象没有这个属性)
其构造函数函数.prototype是一个普通对象,所以具有__proto__属性,它指向的是Object.prototype(Object可以看做是所有对象的构造函数),即:
其构造函数函数.prototype.__proto__===Object.prototype Object.prototype里包含的就是所有对象共有的属性和方法(如typeof,toString)
Object.prototype作为一个对象,也有__proto__属性 Object.prototype.__proto__===null 读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。如果寻找某个不存在的属性,将会遍历整个原型链。
constructor属性:
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承,所以:实例对象.constructor===其构造函数.prototype.constructor(构造函数自身是没有constructor属性的),constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。如果不能确定一个实例对象的constructor属性是什么函数,可以通过它的构造函数里的name属性,从实例得到构造函数的名称,即实例对象.constructor.name===其构造函数的名字。
包装对象
三个原始类型,数值,字符串,布尔值,因为不是对象,所以不能使用对象的方法,所以如果想对它们使用对象的方法,JS内部会先把它们包装成一个对象,然后对这个包装对象调用对象具有的属性和方法,调用完之后,立即销毁这个包装对象,当再次对同一个原始类型的值调用即使相同的方法,所生成的包装对象也不是同一个了。自动转换生成的包装对象是只读的,无法修改,所以不能通过直接对原始类型采用或添加对象的方法直接改变它们的值。但是可以在 Number.prototype,String.protorype,Boolen.prototype上添加属性和方法,添加后原始类型的值也可以直接调用了(先转化成包装对象,包装对象具有这些新添加的属性和方法)
var a=1; //typeof(a)为"number"
var b=new Number(1);// typeof(b)为"object"
a==b//true因为Number(b)为1
a===b//false
console.log(a.__proto__===b.__proto__) //true console.log(b.__proto__===Number.prototype)//true console.log(Number.prototype.__proto__===Object.prototype) //true console.log(Number.__proto__===Function.prototype) //true(Number,String.Boolen的构造函数都是Function) console.log(Function.prototype.__proto__===Object.prototype) //ture console.log(Function.__proto__===Function.prototype) //true(Function同时又是它自身的构造函数)
instanceof 运算符
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。instanceof运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。
由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true。
因为Object是所有对象的构造函数,所以任何对象 instanceof Object都返回true,除非左边对象的原型链上,只有null对象。这时,instanceof判断会失真。
instanceof运算符的一个用处,是判断值的类型。
instanceof运算符只能用于对象,不适用原始类型的值。
undefined和null,instanceOf运算符总是返回false(虽然typeof(null)返回 "object",但null的原型链上只有null,不指向任何构造函数,包括Object)。