js面向对象基础
javascript
在new xxx
的时候发生了什么?
大家在日常的js
开发中,会经常见到类似于下面的代码块:
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.sayName = function(){
console.log('this cat name is:'+this.name);
}
Cat.prototype.sayAge = function(){
console.log('this cat age is:'+this.age);
}
var cat = new Cat('大黄','黄色');
到这边的话,我们就成功创建了一个Cat
类的对象cat
。(注意下,虽然这边我们仍然谈类,但是由于JS是一门鸭子类型的语言,只要具备某个类一定的特性,比如某些方法、属性,那么我们就认为这个对象就是这个类的)
那么在cat对象的创建过程中,又发生了那些事情呢?创建一个对象,一般会经历如下几个步骤:
创建一个空对象
o
令
o
的原型指向函数的原型在新创建的对象
o
上执行函数,即function.apply(o,arguments)
返回对象o
经过以上四个步骤我们就可以创实例化一个Cat类型的对象。
这里说个大家比较容易出错的地方,就是function优惠返回值的情况。比如:
function F(name){
this.name = name;
return "F";
}
这边如果执行var f = new F("warjiang")
;js
在new F('warjiang')
的时候回忽略F的返回值。f最后是一个对象,而不是"F"
.执行结果如下:
什么时候用prototype
,什么时候用this
?
接着上面的例子,我们在定义Cat
类的时候,对于name,color
这些属性的定义是采用的this
的方式。而对于sayName,sayAge
的定义是采用的prototype
的方式。那么什么时候该用this,什么时候该用prototype呢?在说这个问题之前,我想重新再提一下==
,===
。相信每个jser都应该知道这个两个等号与三个等号的区别,两个等号只比较数值大小,不关心类型;三个等号不仅比较值大小,还要比较类型是否一致。下面我们先看个例子:
// number类型
var num1 = 22;
var num2 = 22;
console.log(num1 == num2);//true
console.log(num1 === num2);//true
// string类型
var str1 = "hello world";
var str2 = "hello world";
console.log(str1 == str2)//true
console.log(str1 === str2)//true
// 对象
var student1 = {
name:"tony",
age:22
};
var student2 = {
name:"warjiang",
age:22
}
console.log(student1 == student2);//false
console.log(student1 === student2);//false
// Date类型
var d1 = new Date('2017-3-15 10:23:00');
var d2 = new Date('2017-3-15 10:23:00');
console.log(d1 == d2);//false
console.log(d1 === d2);//false;
// 自定义类型
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.sayName = function(){
console.log("my name is " + this.name);
}
var cat1 = new Cat("大黄","黄色");
var cat2 = new Cat("大黄","黄色");
console.log(cat1 == cat2);//false
console.log(cat1 === cat2);//false
通过上面的例子,我们可以总结出,在js中除了基本类型bool,string,number,undefined,null
是按照value的方式进行相等的比较之外,其余的对象(不管是自定义的还是JS内置的)在做比较的时候都是比较两个对象的内存地址,如果两个对象的内存地址不相等,则表示两个对象就是不相等的。所以才会出现上面例子中的,即使连个对象中所有的属性都相等,仍然会出现两个对象不相当的情况。
ok回到正题上了,有了这边等号比较的基础之后,我们在来看看之前的问题,什么时候应该使用prototype,什么时候应该使用this。再来个?
function Cat(name,color){
this.name = name;
this.color = color;
this.sayColor = function(){
console.log(this.color);
}
}
Cat.prototype.sayName = function(){
console.log("my name is:"+this.name);
}
var cat1 = new Cat("大黄","黄色");
var cat2 = new Cat("大白","白色");
console.log(cat1.sayColor == cat2.sayColor);//false
console.log(cat1.sayName == cat2.sayName);//true
上面例子中两个Cat类型的对象cat1
,cat2
在内存中是什么情况呢。
通过上面的内存图,我们可以知道cat1
的sayColor
与cat2
的sayColor
方法分别指向两块内存地址,所以cat1.sayColor==cat2.sayColor
为false
,而cat1.sayName
与cat2.sayName
都是来源于prototype
对象上的sayName
方法,他们的内存地址其实是一个,故而cat1.sayName == cat2.sayName
方法为true
。
通过上面的两个例子,我们不难看出,如果我们使用this的话,则this上的属性、方法都是属于对象本身的,一般可以用于私有方法、属性,而如果使用prototype的话,prototype上的属性、方法在各个类对象上面共享,一般可以用于共有方法、属性。
因此对于一些共有的方法、属性,我们可以放在prototype上面,而对于一些私有的方法、属性,我们可以放在prototype上
下面的话,我再补充一个额外的例子
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.sayType = function(){
console.log(this.type);
}
var cat1 = new Cat("大黄","黄色");
var cat2 = new Cat("大白","白色");
cat1.sayType();//猫科动物
cat1.type = "狗科动物";
cat2.sayType();//猫科动物
cat1.sayType();//狗科动物
这边的话,可能有些同学会认为cat1.type = "狗科动物"这句话,修改的是prototype中的type,但是实际上cat1.type="狗科动物",只是在cat1对象上面增加了一个type属性,值为"狗科动物",而原型上面的type并没有收到影响。我们可以在控制台中查看cat1与cat2的详细如下:
如果想修改prototype中的type的话,可以把cat1.type
修改为cat1.__proto__.type = "狗科动物"
(这边也要注意下,__proto__
并不是所有浏览器都支持);此时执行结果就像大家想的那样分别为猫科动物,狗科动物,狗科动物
。
JavaScript的继承
谈到js的继承,首先先了解下js中原型链。每个js对象都有一个prototype的属性,这个属性会指向一个新的对象,这个新的对象也会有一个prototype的属性。这样一直到Object.
这边稍微提下,有些人可能会问浏览器中__proto__
与prototype
的区别,可以理解为__proto__
是chrome、firefox
这些浏览器对prototype
的一种实现、表现,东西还是一个东西。原型链我也举个例子
var o = {
name:'warjiang',
age:24
}
var b = {
name:'bb'
}
b.__proto__ = o;
console.log(b.name);//bb
console.log(b.age);//24
console.log(b.__proto__.name);//warjiang
讲完原型链,我们就开始开始讲讲继承。这边的话,我看也有人说用属性拷贝或者是方法借用这种方式来实现继承,不过我不是非常认可。这边我主要用讲原型来实现继承。
用原型链实现继承,就是让子类的prototype指向父类的prototype,比如下面这样:
function Parent(){
this.type = "parent";
}
Parent.prototype.sayType = function(){
console.log(this.type);
}
function Child(){
}
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
但是如果这样写会有一个问题,就是我们在修改子类prototype的constructor的同时也修改了父类的prototype的constructor,这种情况是不允许的。那么解决的思路一般就是两种,一个是通过new Parent()来解决,我们知道new Parent()的过程帮我们创造一个prototype指向Parent的prototype的对象(记作o,o.prototype=parent.prototype),这个时候,让子类prototype指向o这个对象的时候,Child.prototype->o
,o.prototype->Parent.prototype
,这样一个原型链就这么链接起来了,同时这个时候如果去修正Child.prototype.constructor为Child的时候,相当于执行o.constructor = Child
,并不会对Parent.prototype
造成影响。这种做法的表现形式如下:
function Parent(){
this.type = "parent";
}
Parent.prototype.sayType = function(){
console.log(this.type);
}
function Child(){
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
此时如果我们去new Child()对象的时候,我们可以如下的继承图
分析Child.prototype=new Parent();
这个过程我们会发现,new Parent()其实对Parent的prototype起到保护的作用,因此我们完全可以通过一个空对象来完成这样的功能,如下:
function Parent(){
this.type = "parent";
}
Parent.prototype.sayType = function(){
console.log(this.type);
}
var f = function(){}
f.prototype = Parent.prototype;
function Child(){
}
Child.prototype = new f();
Child.prototype.constructor = Child;
这么做的好处在于new f()的过程相对于new Parent()的过程更加轻量,更加节约内存。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。