javascript原型链

 

一、ECMAScript中描述了原型链的概念。我们知道ECMAScript并不像C++,Java那样使用类,但是对象仍然可以通过多种方式创建,其中就有构造函数方式。每个构造函数都有一个原型对象,同时都有一个prototype属性, prototype属性指向构造函数的原型对象,它被用来实现基于原型的继承和共享。而原型对象又都默认会取得一个constructor属性,这个属性包含一个指向构造函数(prototype属性所在函数)的指针。每个通过调用构造函数创建的实例对象都拥有一个指向原型对象的指针。

所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。函数是可调用的对象,所有函数的默认原型对象都是Object的实例

function Person(){}
Person.prototype = {
  name: "bella",
  age: 21,
  sayHello: function(){
    alert(this.name);
  }d.
}

重写之后,构造函数Person的原型对象的constructor属性不再指向Person了,因为该语法的本质是完全重写了默认的原型对象,所以constructor属性也就变成了新对象的constructor属性,指向Object构造函数,我们此时就不能通过constuctor来确定对象的类型了。可以通过Person.prototype.constructor = Person恢复constructor的指针。

对象实例引用属性首先被查询是否包含该属性名,如果包含,该属性值就是我们想获取的,查询停止,如果不包含,会接着查询该对象的原型是否包含该属性,依此类推。我们可以随时动态地为原型添加属性和方法。而且,基于这种搜索过程,我们对原型对象所做的任何修改都能立即从对象实例上看到,即使该修改是在创建实例之后。但如果是用上面提到的语法重写整个原型对象就另当别论了。因为重写原型对象会切断现有原型对象与原来已经存在的任何对象实例之间的联系。Person.prototype.constructor = Person恢复指针

存在问题
原型模式使得所有对象实例在默认情况下取得相同的属性值,对于属性值为函数的情况,这正是我们希望看到的,所有对象实例共享这一函数而不需要重复定义,但是对于属性值为基本值的情况,我们通常希望不同的对象实例拥有不同的基本值,不过,我们可以通过在对象实例上添加同名属性来隐藏原型对象中的属性。但是,如果包含引用类型值的属性,问题就显现出来了。
function Person(){}
Person.prototype = {
  name: "bella",
  age: 21,
  classmates: ["Lucy", "Lily"],
  sayHello: function(){
    alert(this.name);
  }
}
var person1 = new Person();
var person2 = new Person();
person1.classmates.push("Mark");
alert(person1.classmates === person2.classmates);  //true

二、 prototype与__proto__的区别

两者都是对象类型的属性,并非所有的对象类型都有prototype属性,一般只有function对象才有prototype属性。__proto__才是真正的原型链的实际指针,然而许多浏览器并不对外公开这个属性

__proto__默认的也有指向。它指向的是最高级的object.prototype,而object.prototype的__proto__为空。prototype指向一块内存,被new的实例继承。constructor指向创建它的function。

一个函数在创建过程
function A() {};
1、创建一个对象(有constructor属性及[[Prototype]]属性),根据ECMA,其中[[Prototype]]属性不可见、不可枚举
2、创建一个函数(有name、prototype属性),再通过prototype属性 引用 刚才创建的对象
3、创建变量A,同时把函数的 引用 赋值给变量A
那么什么是构造函数呢? 按照ECMA的定义 Constructor is a function that creates and initializes the newly created object.
构造函数是用来新建同时初始化一个新对象的函数。任何一个函数都可以是构造函数。
 
通过构造函数来创建对象的方式。创建对象为什么要new创建而不是直接var a1 = {};
new 操作可以分为三步:
1、新建一个对象并赋值给变量a1:var a1 = {};
2、把这个对象的[[Prototype]]属性指向函数A的原型对象:a1.[[Prototype]] = A.prototype
3、调用函数A,同时把this指向1中创建的对象a1,对对象进行初始化:A.apply(a1,arguments)
继承:
通过对象的[[Prototype]]保存对另一个对象的引用,通过这个引用往上进行属性的查找,这就是原型链。有了原型链的概念,就可以进行继承。
function B() {};
产生了B的原型B.prototype,里面数据实际上就是实际上就是 {constructor : B , [[Prototype]] : Object.prototype}

第一种是通过改变原型链引用地址

B.prototype.__proto__ = A.prototype

ECMA中并没有__proto__这个方法。new操作的时候,实际上只是把实例对象的原型链指向了构造函数的prototype地址块

B.prototype = new A();
这样B产生的对象的构造函数发生了改变,因为在B中没有constructor属性,只能从原型链找到A.还要人为设回B本身
B.prototype.constructor = B;
三、基本应用

1、面向对象应用OOP

var decimalDigits = 2,
    tax = 5;
function add(x, y) {
  return x + y;
}
function subtract(x, y) {
  return x - y;
}
alert(add(1, 3));
//通过new 一个对象就可以调用里面的公开的方法、属性。
var Calculator = function (decimalDigits, tax) {
    this.decimalDigits = decimalDigits;
    this.tax = tax;
};
Calculator.prototype = {
    add: function (x, y) {
        return x + y;
    },

    subtract: function (x, y) {
        return x - y;
    }
};
alert((new Calculator()).add(1, 3));
//利用函数自执行在加载文件同时,执行上面的JS代码,那么我们就可以访问对外公开的方法和属性,如果不通过自执行,则会报异常
var Calculaotr = function(x, y) {
                this.x = x;
                this.y = y;
            };
            Calculaotr.prototype = function() {
                function add(x,y) {
                    return x + y;
                };
                function subtract(x,y) {
                    return x - y;
                };
                return {
                    A:add,
                    S:subtract
                }
            }();

2、继承

var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = {
    A: function(x, y) {
        return x + y;
    },
    S: function(x, y) {
        return x - y;
    }
};
var Calculator = function() {
    this.tax = 3;
};
Calculator.prototype = new BaseCalculator();
//类A的一个属性是类B型,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。
//不想让Calculator对象访问BaseCalculator的decimalDigits属性
var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = {
    A: function(x, y) {
        return x + y;
    },
    S: function(x, y) {
        return x - y;
    }
};
var Calculator = function() {
    this.tax = 3;
};
Calculator.prototype = BaseCalculator.prototype;

//重写原型
Calculaotor.prototype.add = function(x, y) {
    return x + y + this.tax;
}

3、属性查找

当查找一个对象的属性时,会遍历原型链,一直往顶层Object找,如果没有找到,则返回undefined.

function foo() {
            this.add = function (x, y) {
                return x + y;
            }
        }

        foo.prototype.add = function (x, y) {
            return x + y + 10;
        }

        Object.prototype.subtract = function (x, y) {
            return x - y;
        }

        var f = new foo();
        alert(f.add(1, 2)); //结果是3,而不是13
        alert(f.subtract(1, 2)); //结果是-1
//add函数返回的是3,而不是13则说明,属性查找时,优先查找自己的属性。然后在往上一级找,最后找Object,这样看来,在遍历时用for in效率就是个问题。

hasOwnProperty是判断一个对象是否包含自定义属性而不是原型链上的属性,是JS中唯一一个查找属性,但不查找原型链的函数。但是JS不会保护hasOwnProperty函数,如果刚好某个对象中也有hasOwnProperty函数,则我们可以通过以下方式正确获得想要的结果:

alert({}.hasOwnProperty.call(c, 'tax'));//返回true
//这里的c是Calculator的一个对象,tax是我们要找的属性。
Object.prototype.bar = 1;
    var foo={moo : 1}
    for (var i in foo) {
        if(foo.hasOwnProperty(i)) {
            alert(console.log(i));
        }
    }//此时只会输出moo属性

 

posted @ 2016-01-11 14:17  圣耀  阅读(224)  评论(0编辑  收藏  举报