JavaScript面向对象编程

Posted cherryones

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript面向对象编程相关的知识,希望对你有一定的参考价值。

今天学习的内容是JS面向对象编程,以下总结的所有内容都来自于阮一峰大大的网络日志,感兴趣的小朋友可以戳原链接

 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html 

真,通俗易懂,同时上一次面试的第一道题(JS继承)也在我的脑海中逐渐立体起来。

JS,一门没有Class类的面向对象编程(OOP)语言,我的第一个问题是如何生成实例化对象?

 假设我们有一个对象Cat,现在需要实例出一只具体的“猫”,很容易写出下面的代码:

1 var cat1 = {};
2 cat1.name = "Tom";
3 cat1.color = "black";

定义对象并给对象赋属性的这种写法就可以看作是 最原始的生成Cat实例对象 的方法。我们当然不会傻到需要多少个实例,就写多少这样类似的代码,于是我们可以继续写成这样:

1 function Cat(name, color){
2      return {
3          name: name,
4          color: color
5      }
6  }
7 var cat1 = Cat("Tom", "black");
8 var cat2 = Cat("Lucy", "white");

这显然是 调用函数 的写法,但是这样不行,看不出两个实例对象的联系??(什么意思啊喂)

然后顺其自然的进入到 构造函数 的写法(补充一下对构造函数的定义:普通函数中使用this,对构造函数new一下,就可以产生实例对象啦~)

1 function Cat(name, color){
2     this.name = name;
3     this.color = color;
4 }
5 var cat1 = new Cat("Tom", "black");
6 var cat2 = new Cat("Lucy", "white");

说实话这种写法跟上面调用函数的有点像呢,但是构造函数有属性加持啊,这下可以通过“constructor”属性以及“instanceof”运算符查看两个实例对象的关系了,虽然我现在依然不知道干嘛要这样。

每一个实例化对象的constructor属性都是指向原型对象的!!! // cat1.constructor == Cat => true

instanceof 同理咯 // cat1 instanceof Cat == true

但是构造函数里有恒定的属性或者方法会怎么样呢?通过每一次实例化构造函数,都会产出一个相同的属性或方法。假设程序中需要对原型对象多次实例化,这样就会产生占用内存的弊端,于是下一个方法又要出场了!!

prototype属性

1 function Cat(name, color){
2     this.name = name;
3     this.color = color;    
4 }
5 Cat.prototype.kind = "猫科动物";
6 Cat.prototype.eat = function () {
7     alert(‘我喜欢吃杰瑞‘);   
8 }

引入官方术语,每一个构造函数都有一个prototype属性,这个属性指向另一个对象,而实例对象会继承到这个对象上的属性和方法,只要把不变的属性以及方法绑定在这个对象上,它们就会指向同一个内存空间,你可以这样测试:

1 var cat1 = new Cat("Tom", "black");
2 var cat2 = new Cat("Lucy", "white");
3 console.log(cat1.eat == cat2.cat); // true

而纯粹使用构造函数的写法,这个时候控制台会输出 false ,说明它们实际指向不同的内存空间。

扩展:

技术分享图片

感觉不太常用就不再手动敲一遍啦~

接下来是 in 运算符的相关使用,想必大部分人都是很熟悉这块的,用来判断对象是否含有某个属性以及遍历属性。(但是上次我竟然没回答对,哎

1 console.log(name in cat1); // true
2 
3 for (var key in cat1) {
4    console.log("attr-"+ key + ":" + cat1[key]);
5 }

嗯,又深刻理解了一遍生成原型实例的四种方法!按照计算机语言的套路,完成了封装自然要讨论如何继承。

构造函数的五种继承方法

现在已经有Cat对象,新增父对象Animal,如何让Cat对象继承Animal对象?


1 function Cat(name, color){  //子对象
2     this.name = name;
3     this.color = color;
4 } 
5 function Animal(species){  //父对象
6     this.species = "猫科动物";
7 } 
  • 使用apply()绑定

 

1 function Cat(name, color){
2     Animal.apply(this, arguments);
3     this.name = name;
4     this.color = color;   
5 }

 

  • 使用prototype模式,将子对象的prototype对象指向父对象的实例
1 Cat.prototype = new Animal();  
2 Cat.prototype.constructor = Cat; // 修改了Cat的prototype对象后,也会把该对象原本对Cat的指向修改成Animal, 此时需要手动重新把指向还原

但是!实例化对象也是要占用内存的不是,于是这种写法也是不被提倡的,于是有了下面的改进写法。

注意:以下所有修改了prototype对象的代码下面,都需要重新还原指向!!!

  • prototype模式改进
1 Cat.prototype = Animal.prototype;
2 Cat.prototype.constructor = Cat;

看起来蛮不错的写法,却会产生这样的问题:第一行代码执行完毕后,为了让Cat.prototype指向还原,于是我们写了第二行代码,万万没想到,第二行代码执行完毕后,Animal.prototype的指向也变成了Cat!!

并且后续对Cat.prototype的任何修改都会影响到Animal.prototype,这样显然是不合理的。

但是我们可以把第二种和第三种写法结合一下,产生下面这张写法:

  • 利用空对象做中介
1 function extend(child, parent){
2     var f = function () {}
3     f.prototype = parent.prototype;
4     child.prototype = new f();
5     child.prototype.constructor = child;
6 }

实例化空对象是几乎不占内存的,因此排除了上述两种写法的弊端。

  • 拷贝继承
 1 function copyExtend(child, parent) {
 2     var c = child.prototype;
 3     var p = parent.prototype;
 4     for (var key in p){
 5         c[key] = p[key];
 6     }
 7     c.uber = p;
 8 }
 9 
10 function Animal(){}
11 Animal.prototype.species = "猫科动物";
12 
13 //调用
14 copyExtend(Cat, Animal);

讲完构造函数的继承,接下来是 非构造函数的继承

其实普通函数的继承原理和构造函数还是类似的,主要也是利用prototype属性以及属性拷贝的方法。

重新定义两个对象:

var china = {   // 父对象
    nation: "中国";
}
var doctor = {  // 子对象
    career: "医生"
}

1、第一个方法:我们来定义一个object函数

function object(parent){
    function f() {}
    f.prototype = parent;
    return new f();
}
var doctor = object(country);
doctor.career = "医生"

object() 的作用,我们都知道构造函数的prototype属性都是指向另一个对象的(实例对象是不存在这个属性的),第二句代码相当于把父对象强行设定为这个“另一个对象”。而实例化对象(new f())会自动继承"另一个对象"的所有属性和方法,这个时候返回的实例对象实际已经具备父对象的属性和方法,再补充上子对象的属性不就刚好完成了对父对象的继承?非常巧妙。

2、第二个方法,依旧是拷贝属性,此处掠过浅拷贝的方法,直奔最完美答案——深拷贝

 1 function deepCopy(child, parent){
 2     child = child || {};
 3     for (var key in parent) {
 4         if (typeof(parent[key])) === ‘object‘) {  // 判断属性是否是对象
 5              child[key] = (parent[key].constructor === Array) ? [] : {};  // 判断属性对象是数组对象还是纯对象
 6              deepCopy(child[key], parent[key]); // 递归拷贝
 7         } else {
 8              child[key] = parent[key]; 
 9         }
10     }
11     return child;
12 }        

不同于浅拷贝,深拷贝后的子对象继承了父对象的属性以后,即使修改了继承自父对象的属性,也不会影响到原父对象属性的内容。

我们来测试一下:

1 china.city = ["北京", "上海", "深圳"]; // 新增一个父对象的对象属性
2 
3 // 调用深拷贝方法 
4 var doc = deepCopy(doctor, china);
5 doc.city.push("杭州");
6 
7 console.log(doc.city);  //  ["北京", "上海", "深圳", "杭州"]
8 console.log(china.city); //  ["北京", "上海", "深圳"]

以上,关于javascript面向对象编程的一些比较入门的知识点有了比较深刻的认识。写博文实在是太累了~还是站在巨人的肩膀上,以后还是要多写多总结啊!!!

 

 

 

 


 





以上是关于JavaScript面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章

javascript 仿面向对象编程实例代码(私有,公共变量。。。)

JavaScript对象原型链继承闭包

JavaScript对象原型链继承和闭包

javascript: 基于原型的面向对象编程

《JavaScript函数式编程思想》——从面向对象到函数式编程

面向面试编程代码片段之GC