Js 面向对象之封装,继承
封装 ,继承
封装 ?
面向对象有三大特性,封装、继承和多态。对于ES5来说,没有class(类)的概念,并且由于JS的函数级作用域(函数内部的变量在函数外访问不到),所以我们就可以模拟 class (类)的概念,在ES5中,类其实就是保存了一个函数的变量,这个函数有自己的属性和方法;将属性和方法组成一个类的过程就是封装。
那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢?
原始模式
function Cat (name, color) { return { name: name, color: color } } var cat1 = Cat("大毛", "黄色");//{name: "大毛", color: "黄色"} var cat2 = Cat("二毛", "黑色");//{name: "二毛", color: "黑色"}
这种模式并不能看出来 cat1 和 cat2 是同一个原型对象的实例
构造函数模式
为了解决从原型对象生成实例的问题,Js提供了一个构造函数(Constructor)模式。
所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量,对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上
function Cat (name, color) { this.name = name; this.color = color; this.age = "5"; } var cat1 = new Cat("大毛", "黄色"); var cat2 = new Cat("二毛", "黑色"); cat1.constructor == Cat;//true cat2.constructor == Cat; //true cat1.constructor == cat2.constructor//true
JS 还提供了 instanceof 运算符,用来校验原型对象与实例对象的关系
cat1 instanceof Cat ;// true
表面上好像没什么问题,但是实际上这样做,有一个小小的不优雅,那就是对于每一个实例对象,age属性都会生成一遍,每生成一个实例,都必须为重复的内容,多占用一些内存,这样既不环保,也缺乏效率。那么有办法解决吗 ? 答案当然是有的:
Prototype模式
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上;
function Cat (name, color) { this.name = name; this.color = color; } Cat.prototype.age = "10"; var cat1 = new Cat("大毛", "黄色"); var cat2 = new Cat("二毛", "黑色"); cat1.age; // 10; cat2.age; // 10;
这时所有实例的age属性,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率;
继承 ?
Prototype 原型继承
// 创建父构造函数 function SuperClass(name){ this.name = name; this.showName = function(){ alert(this.name); } } // 设置父构造器的原型对象 SuperClass.prototype.Age = '123'; // 创建子构造函数 function SubClass(){} // 设置子构造函数的原型对象实现继承 SubClass.prototype = SuperClass.prototype; //生成实例 var child = new SubClass() child.name // undefined child.Age // 123
上述的Prototype模式已经实现了继承,但仅仅继承了父构造函数的prototype 原型上的成员,并不能继承父构造函数的其它成员,那么还有别的方法来实现继承嘛 ? 答案当然是有的;
原型链继承
//即 子构造函数.prototype = new 父构造函数() // 创建父构造函数 function SuperClass(){ this.name = 'HiSen'; this.age = 25; this.showName = function(){ console.log(this.name); } } // 设置父构造函数的原型 SuperClass.prototype.friends = ['js', 'css']; SuperClass.prototype.showAge = function(){ console.log(this.age); } // 创建子构造函数 function SubClass(){} // 实现继承 SubClass.prototype = new SuperClass(); // 修改子构造函数的原型的构造器属性,因为此时继承了父构造函数指向 //SuperClass; 所以要修改一下。 SubClass.prototype.constructor = SubClass; //生成实例 var child = new SubClass();
console.log(child.name); // HiSen console.log(child.age);// 25 child.showName();// HiSen child.showAge();// 25 console.log(child.friends); // ['js','css'] // 当我们改变friends的时候, 父构造函数的原型对象的也会变化 child.friends.push('html'); console.log(child.friends);// ["js", "css", "html"] var father = new SuperClass(); console.log(father.friends);// ["js", "css", "html"]
此时再看:发现子构造函数 不仅继承了父构造函数原型 prototype 上的成员,也继承了其它成员。可是修改子构造函数的属性时,我们发现父构造函数的原型对象也对应修改,那有没有办法屏蔽这一种情况呢 ? 接着往下看:
拷贝实现继承
说到拷贝,可能会分深拷贝和浅拷贝,其实:
浅拷贝是对象的属性的引用,而不是对象本身; (浅拷贝只拷贝一层,如果存在多层还是会影响原对象)
深拷贝是创建一个新的内存地址保存值 ; (与原对象互不影响)
下边我列举两个拷贝的方法来实践一下:
浅拷贝
例举一个简单的浅拷贝: 对象形式
function clone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; }
深拷贝
对象形式的深拷贝
function clone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { if (typeof source[i] === 'object') { target[i] = clone(source[i]); // 注意这里 } else { target[i] = source[i]; } } } return target; }
数组形式的深拷贝
function clone(source) { var out = [],i = 0,len = source.length; for (; i < len; i++) { if (source[i] instanceof Array){ out[i] = clone(arr[i]); } else out[i] = source[i]; } return out; }
当然出了以上这些实现继承的方法以外还有更多的方式同样可以实现继承,例如:
Object.create();继承
ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;
obj:一个对象,应该是新创建的对象的原型。
propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与
Object.defineProperties()
的第二个参数一样)。注意:该参数对象不能是 undefined
,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。var obj = {
"a":'123',
fun :function () {
alert(1)
}
}
var jc = Object.create(obj);
jc.a; //123
jc.fun();//1
我们可以看到,jc 继承了 obj 的属性;同时也继承了 obj 对象的方法;
ES6钟提供了一个方法 Object.assign();
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。Object.assign
是ES6的新函数。Object.assign()
Object.assign(target, ...sources)
target:目标对象。
sources:任意多个源对象。
var obj = { a: {a: "hello", b: 21} }; var initalObj = Object.assign({}, obj); initalObj.a.a = "changed"; console.log(obj.a.a); // "changed"
最后再说一种最简单的方式,转成字符串 - 再转回来;
var obj1 = { o: { a: 1 } }; var obj2 = JSON.parse(JSON.stringify(obj1));
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。敬请期待...