1. 构造函数,原型与实例
JavaScript诞生的时候,为了使语言简单易学,没有引入当时流行的面向对象编程中的“类”class,为了实现继承,作者做了一个简化的设计,由于C++和Java使用new命令时,都会调用"类"的构造函数,在Javascript语言中,new命令后面跟的不是类,而是直接写构造函数(constructor)。
// 构造函数
function Man(){
this.name = name;
}
// 创建实例
let man1 = new Man('张三');
用构造函数生成实例对象时,无法共享属性和方法。并且,如果我们在构造函数中定义了方法,那么这个方法会在每个实例上都重新创建一遍,会产生不同的作用域和标识符,导致不同实例上的同名方法是不相等的。
function Man(){
this.name = name;
this.sex = "male";
this.sayName = function(){
return this.name
}
}
let man1 = new Man('张三');
let man2 = new Man('李思');
//如果修改man2的sex属性,man1不会改变
man2.sex = "female";
console.log(man1.sex); //male
//不同实例创建了不同的方法
console.log(man1.sayName == man2.sayName); //false
为了节省资源和数据共享,引入了prototype原型对象。用来存放所有实例对象需要共享的属性和方法,而不需要共享的方法就放在构造函数中。实例对象一旦创建,将自动引用prototype对象的属性和方法。
function Man(){
this.name = name;
}
// 存放实例共享的属性和方法country
Man.prototype.country = ‘china’
let man1 = new Man('张三');
let man2 = new Man('李思');
console.log(man1.country); //china
console.log(man2.country); //china
2.原型链的基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例都包含一个指向原型对象的内部指针__proto__。 如果我们将原型对象等于另一个类型的实例,此时的原型对象包含指向另一个原型的指针,另一个原型对象中也包含指向另一个构造函数的指针,假设另一个原型对象又是其他类型的实例,像这样层层递进,就形成了实例与原型的链条,这就是原型链,下图为链条的两节。
proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
function Man(){
this.name = name;
}
//在构造函数man的原型上定义一个属性
Man.prototype.needFoods = true;
function chinese(){
this.country = 'china';
}
//将men的实例作为chinese的原型
chinese.prototype = new Man()
//在chinese的原型上添加一个属性,添加的属性一定要放在原型替换语句之后,否则会undefined
chinese.prototype.speak = "chinese"
let man1 = new chinese('张三');
console.log(man1.needFoods); //true
console.log(man1.speak); //chinese
构造函数(最顶端)的原型是Function.prototype
Function.prototype的原型是Object.prototype
Object.prototype的原型是null(走到头了)
console.log(Man.__proto__ == Function.prototype); // true
console.log(Man.__proto__.__proto__ == Object.prototype); // true
console.log(Object.prototype.__proto__);//null
3.判断对象类型的方法
A instanceof B
检测B构造函数的 prototype 属性是否出现A的原型链上
console.log(man1 instanceof Man) //true
console.log(man1 instanceof chinese) //true
console.log(man1 instanceof Object) //true
B.isPrototypeOf(A)
用于检测B的原型对象是否存在于A的原型链上,与instanceof
不同的是,它是通过[[Prototype]](_proto_)来直接判断。需要带上prototype。
console.log(Man.prototype.isPrototypeOf(man1)) //true
console.log(chinese.prototype.isPrototypeOf(man1)) //true
console.log(Object.prototype.isPrototypeOf(man1)) //true
Object.getPrototypeOf(A)
该方法会直接返回A.__proto__的值
console.log(Object.getPrototypeOf(man1)) // Man {name: "", speak: "chinese"}
console.log(Object.getPrototypeOf(man1) == chinese.prototype) //true
hasOwnProperty
用于检测一个属性是存在于实例中
console.log(man1.hasOwnProperty('country')) // 实例属性 true
console.log(man1.hasOwnProperty('speak')) // speak定义在原型上 false
但是,在hasOwnProperty返回false的时候,也有可能是不存在该属性,如何判断该属性在原型上呢?
in
操作符用来判断一个对象能否访问到某个属性
console.log('country' in man1) // true
console.log('speak' in man1) // true
如果某个属性在in
操作符下返回true&&在hasOwnProperty
下返回false,就可以确定该属性是原型上定义的属性了。