--- layout: post title: "JavaScript 面向对象" date: 2015-06-15 14:06:05 categories: JavaScript tags: JavaScript 面向对象 慕课网 ife --- * content {:toc} 本文为慕课网 [JavaScript深入浅出](http://www.imooc.com/learn/277) JavaScript 面向对象笔记。 ## 概念 > 面向对象程序设计(Object-oriented programming,OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。 > > ——维基百科 一般面向对象包含:继承,封装,多态,抽象 --- ## 基于原型的继承 function Foo() { this.y = 2; } console.log(typeof Foo.prototype); //object Foo.prototype.x = 1; var obj3 = new Foo(); console.log(obj3.y); //2 console.log(obj3.x); //1 创建函数 `Foo` 的时候,就会有一个内置的 `Foo.prototype` 属性,并且这个属性是对象。 在使用 `new Foo();` 创建对象实例时。`this` 会指向一个对象,并且这个对象的原型会指向 `Foo.prototype` 属性。`this.y = 2` 给这个对象赋值,并把这个对象返回。把这个对象赋值给 `obj3`。 `y` 是 `obj3` 上的,`x` 是 `obj3` 的原型 `Foo.prototype` 上的。 ![prototype](http://7q5cdt.com1.z0.glb.clouddn.com/blog-prototype.png) --- ### prototype 属性与原型 prototype 是函数对象上预设的对象属性。 原型是对象上的原型,通常是构造器的 prototype 属性。 --- #### 例 function Person(name, age) { this.name = name; this.age = age; } Person.prototype.LEGS_NUM = 2; Person.prototype.ARMS_NUM = 2; Person.prototype.hi = function() { console.log('Hi, my name is ' + this.name + ". I'm " + this.age + ' years old now'); }; Person.prototype.walking = function() { console.log(this.name + ' is walking...'); }; function Student(name, age, className) { Person.call(this, name, age); //使 Person 中的 this 指向 Student this.className = className; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; Student.prototype.hi = function() { console.log('Hi, my name is ' + this.name + ". I'm " + this.age + ' years old now, and from ' + this.className + "."); }; Student.prototype.learn = function(subject) { console.log(this.name + ' is learning ' + subject + ' at ' + this.className + '.'); } //test var gao = new Student('Gao', '24', 'Class 3123'); console.log(gao); // 这个对象的具体内容见下图 gao.hi(); //Hi, my name is Gao. I'm 24 years old now, and from Class 3123. gao.LEGS_NUM; //2 gao.walking(); //Gao is walking... gao.learn('JavaScript'); //Gao is learning JavaScript at Class 3123. * `Object.create(arg)` 创建一个空对象,并且这个对象的原型指向参数 `arg`。 * `Student.prototype.constructor = Student` 为了保证一致性,否则 constructor 指向 Person。 --- ### 原型链 gao 对象的原型链: ![Object](http://7q5cdt.com1.z0.glb.clouddn.com/blog-oop-gao.png) 下面通过图形展示原型链: ![原型链](http://7q5cdt.com1.z0.glb.clouddn.com/blog-原型链.png) --- #### `Object.create(null)` & `.bind(null)` 这两种算是特例。 `Object.create(null)` 和 `.bind(null)` 这两种方式创建出来的对象是没有 `prototype` 属性的,为 `undefined`。 --- ## prototype 属性 ### 改变 prototype JavaScript 中的 prototype 是对象,在运行的时候可以修改。 给 prototype 添加或删除一些属性,是会影响到已经创建好的实例对象的。 但是,直接修改 prototype 属性,是不会影响到已经创建好的实例对象的。但是会影响到新的实例对象。如下代码: // 上接上面的代码 // 给 prototype 添加或删除一些属性 Student.prototype.x = 101; console.log(gao.x); //101 // 直接修改 prototype 属性 Student.prototype = { y: 2 }; // 不会影响到已创建好的实例对象 console.log(gao.x); //101 console.log(gao.y); //undefined // 会影响到新创建的实例对象 var ying = new Student('Ying', 24, 'UI'); console.log(ying.x); //undefined console.log(ying.y); //2 --- ### 内置构造器的 `prototype` 属性 修改内置构造器的 `prototype` 属性后,在实例化这个对象后,枚举其属性时,会把修改的内置构造器的 `prototype` 属性也枚举出来,有时候这是要避免的。可用 `defineProperty` 方法解决。如下代码: Object.prototype.x = 1; var obj = {}; console.log(obj.x); //1 console.log(obj); for (var k in obj) { console.log('result--->' + k); } // result--->x 使用 `defineProperty` 后: Object.defineProperty(Object.prototype, 'x', { writable: true, value: 1 }); var obj = {}; console.log(obj.x);//1 console.log(obj); for (var k in obj) { console.log('result--->' + k); } // nothing output here 其实也可以这样枚举,使用 `hasOwnProperty` 方法: for (var key in obj) { if (obj.hasOwnProperty(key)) { console.log("result--->" + key); } } --- ### 创建对象-new/原型链 ![prototype](http://7q5cdt.com1.z0.glb.clouddn.com/blog-new prototype.png) --- ## instanceof console.log([1, 2] instanceof Array); //true console.log([1, 2] instanceof Object); //true console.log(new Object() instanceof Array); //false 左边要求是对象,右边要求是构造器或函数。它会判断:**右边的构造器中的 `prototype` 属性是否出现在左边的对象的原型链上。** * **注意:**不同的 window 或 iframe 间的对象类型检测**不能**使用 `instanceof`! --- ## 实现继承的方式 function Person() {} function Student() {} Student.prototype = Person.prototype; //1 Student.prototype = new Person(); //2 Student.prototype = Object.create(Person.prototype); //3 Student.prototype.constructor = Student; 注释中: 1 是错误的。如果改变了 Student 就会改变 Person 2 可以实现继承,但是其调用了构造函数,若父类构造函数中有形参,那么传值就会比较奇怪。 3 是最好的方法。创建了一个空对象,并且对象的原型指向参数 Person.prototype。这样便实现了继承。同时原型链写,不向上查找。但是 `Object.create` 是ES5 中的方法,所以可以使用下列代码做兼容: if (!Object.create) { Object.create = function(proto) { function F() {} F.prototype = proto; return new F; }; } --- ## 模拟重载 function Person() { var args = arguments; if (typeof args[0] === 'object' && args[0]) { if (args[0].name) { this.name = args[0].name; } if(args[0].age){ this.age = args[0].age; } } else { if (args[0]) { this.name = args[0]; } if (args[1]) { this.age = args[1]; } } } //重写 toString 方法 Person.prototype.toString = function() { console.log('name='+this.name+', age='+this.age); }; var gao = new Person({name:'Gao',age:24}); gao.toString(); // name=Gao, age=24 var ying = new Person('Ying',25); ying.toString(); // name=Ying, age=25 对参数进行判断,模拟实现重载。 --- ## 调用子类方法 function Person(name) { this.name = name; } function Student(name, className) { this.className = className; Person.call(this, name); // 调用基类的构造器 } var gao = new Student('Gao', '3123'); console.log(gao); // Student {className: "3123", name: "Gao"} Person.prototype.init = function() {}; Student.prototype.init = function() { // do sth... Person.prototype.init.apply(this, arguments); // 同时也想调用父类被覆盖的方法 }; 主要是两种:调用父类的构造器,调用原型链上父类被覆盖的方法。 --- ## 链式调用 function ClassManager() {} ClassManager.prototype.addClass = function(str) { console.log('Class: ' + str + ' added'); return this; }; var manager = new ClassManager(); manager.addClass('classA').addClass('classB').addClass('classC'); // Class: classA added // Class: classB added // Class: classC added 重点在于 return this。返回这个 ClassManager 的实例。这样这个实例又可以继续调用方法。 --- ## 抽象类 在构造器中 `throw new Error('');` 抛异常。这样防止这个类被直接调用。 function DetectorBase() { throw new Error('Abstract class can not be invoked directly!'); } DetectorBase.detect = function() { console.log('Detection starting...'); } DetectorBase.stop = function() { console.log('Detection stopped.'); }; DetectorBase.init = function() { throw new Error('Error'); } var d = new DetectorBase();// Uncaught Error: Abstract class can not be invoked directly! function LinkDetector() {} LinkDetector.prototype = Object.create(DetectorBase.prototype); LinkDetector.prototype.constructor = LinkDetector; var l = new LinkDetector(); console.log(l); //LinkDetector {}__proto__: LinkDetector l.detect(); //Uncaught TypeError: l.detect is not a function l.init(); //Uncaught TypeError: l.init is not a function `var d = new DetectorBase();` 是不能实例化的,会报错 `l.detect();` 但是这个为什么报错我就不知道了。 已经在原课程下提问了,期待老师的讲解。 [抽象类中子类为什么不能调用父类的非抽象方法?](http://www.imooc.com/qadetail/82732) 问题已经解决了,应该是老师当时的课件写错了,应该再基类中将这两个方法写在其原型 prototype 上。如下: function DetectorBase() { throw new Error('Abstract class can not be invoked directly!'); } DetectorBase.prototype.detect = function() { console.log('Detection starting...'); }; DetectorBase.prototype.stop = function() { console.log('Detection stopped.'); }; DetectorBase.prototype.init = function() { throw new Error('Error'); }; // var d = new DetectorBase();// Uncaught Error: Abstract class can not be invoked directly! function LinkDetector() {} LinkDetector.prototype = Object.create(DetectorBase.prototype); LinkDetector.prototype.constructor = LinkDetector; var l = new LinkDetector(); console.log(l); //LinkDetector {}__proto__: LinkDetector l.detect(); //Detection starting... l.init(); //Uncaught Error: Error --- ## 模块化 var moduleA; moduleA = function() { var prop = 1; function func() {} return { func: func, prop: prop }; }(); // 立即执行匿名函数 prop,func 不会被泄露到全局作用域。 或者另一种写法,使用 new moduleA = new function() { var prop = 1; function func() {} this.func = func; this.prop = prop; } 更复杂的可以使用 Sea.js Kissy Require.js 模块化工具。 --- 最后补充一点设计模式相关的资料,我还没有来得及看的: * [学用 JavaScript 设计模式](http://www.oschina.net/translate/learning-javascript-design-patterns) * [常用的Javascript设计模式](http://blog.jobbole.com/29454/) * [JavaScript设计模式深入分析](http://developer.51cto.com/art/201109/288650_all.htm)