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 仿面向对象编程实例代码(私有,公共变量。。。)