首先面向对象是什么, 为什么要面向对象.
因为 JavaScript 对每个创建的对象都会自动设置一个原型 (谷歌火狐中是 proto), 指向它的原型对象 prototype, 举个栗子:
function person() {}
那么 person 的原型链就是:
person(proto)> Function.prototype(proto)> Object.prototype
所以最终的结果都是指向原型对象, 就称为面向对象
那么问题来了, 怎么面向对象呢? 其实就是问怎么创建对象, 创建对象有哪几种方法.
1, 工厂模式 --- 本质就是在函数中创建一个对象, 每次调用时候传入不同的参数, 然后返回这个对象
- // 工厂模式
- function Person(name, age) {
- var p = {};// 创建一个对象
- p.name = name;
- p.age = age;
- p.say = function() {
- console.log("hello"+this.name);
- }
- return p;// 返回这个对象
- }
- var p1 = Person('LL',24);
- console.log(p1);//{name:"LL",age:"24",say:f}
- console.log(p1.say());//hello LL
- var p2 = Person('XX',25);
- console.log(p2);//{name:"XX",age:"25",say:f}
- console.log(p2.say());//hello XX
image.png
2, 构造函数模式 --- 跟工厂模式的区别就是函数中用 this 指代对象也不用 return 了, 还有一个区别就是创建对象的时候是 new 一个对象
- // 构造函数模式
- function Person(name, age) {
- this.name = name;
- this.age = age;
- this.say = function() {
- console.log("hello"+this.name);
- }
- }
- var p1 =new Person('LL',24);
- console.log(p1);//Person {name:"LL",age:"24",say:f}
- console.log(p1.say());//hello LL
- var p2 = new Person('XX',25);
- console.log(p2);//Person {name:"XX",age:"25",say:f}
- console.log(p2.say());//hello XX
image.png
3, 原型方法 --- 在这就先不讲了, 就是通过创建一个 Function 然后在 function.prototype 上添加属性和方法, 再 new 一个 Function 这种方式创建一个对象.
4, 混合模式 (原型加构造函数的模式)--- 就是把实例的属性写在构造函数里面, 每个实例共享的属性和方法写在原型里面. 那么什么是实例的属性呢? 就是通过 new 一个函数都会有的属性就是实例的属性, 它们互不影响各自独立. 那么什么是共享的属性和方法呢? 就是 prototype 里面如果有 sing 这个方法, 那么 new 两个实例后都会有 sing 这个实例的方法. 那么问题来了 new 两个实例之后构造函数里面的属性不是也是都会有的吗? 所以区别就是 prototype 里面的属性和方法是不独立的, 不管 new 多少个实例, prototype 始终指向的地址都是同一个, 也就有了面向对象的省内存, 不浪费资源的优点. 好直接上代码
- // 构造函数里面写上实例的属性和方法
- function Person(name, age) {
- this.name = name;
- this.age = age;
- this.say = function() {
- console.log("hello"+this.name);
- }
- }
- // 原型上写共享的属性和方法
- Person.prototype.sing = function() {
- console.log("Hi"+this.name);
- }
- // 或者这么写
- /*Person.prototype={
- constructor:Person,
- sing:function() {
- console.log("Hi"+this.name);
- }
- }*/
- var p1 =new Person('LL',24);
- console.log(p1);//Person {name:"LL",age:"24",say:f}
- console.log(p1.say());//hello LL
- console.log(p1.sing());//Hi LL
- var p2 = new Person('XX',25);
- console.log(p2);//Person {name:"XX",age:"25",say:f}
- console.log(p2.say());//hello XX
- console.log(p2.sing());//Hi XX
image.png
image.png
那么我们如何来证明一下原型上的 sing 方法是共用的呢? 看上面两个图中, 打印的 p1 和 p2 下面的 proto 中都有 sing:f() 方法, p1 和 p2 调用 sing 方法的时候会在实例属性和方法中查找 sing 方法, 实例中找不到这个 sing 方法, 所以就会在原型链中查找, 这就是所谓的上溯原型链, 在 Person 的 protorype 中找到了 sing 方法, 所以 p1 和 p2 调用的是同一个 sing 方法, 这也就是所谓的共享一个 sing 方法.
那么现在还有一个问题, 为什么 p1 和 p2 是现在自己的实例属性中查找, 找不到再到构造函数的原型中查找呢? 我们来分析一下这个问题, 我把这个原型中 sing 方法改成 say 方法, 那么现在实例属性中有 say 方法, 原型中也有 say 方法了, 打印的时候是打印 Hello+this.name 还是 Hi+this.name 呢, 如果是打印的 Hello+this.name 就成功验证了先在实例属性中查找, 找不到就到原型中查找, 当然找到的话就返回值就不继续查找了, 接着上代码验证一下.
image.png
在 p1 中可以看到, 有两个 say 方法, 打印 p1.say(); 返回的是 Hello LL; 那么也就验证了上述的说法.
来源: http://www.jianshu.com/p/29ebb1385302