javascript学习笔记

关于原型继承

和编译型语言C++以及编译解释型语言JAVA的类继承不同,Javascript采用了基于原型的继承。在javascript语言中,实质没有类,除了基本类型,其他均为对象,包括函数,一个对象继承自另一个对象而不是类。而构造器函数既可以产生对象,也可以当做函数或者对象来对待。OOP概念在javascript上似乎体现的更加明显:万物皆对象!但javascript的目的不是OOP的,函数才是Javascipt的核心。

1.原型继承基本原理

Javascript继承通过原型链来实现,在每个对象内,都有一个保存着该对象所继承的对象引用的属性__proto__,利用该属性,可以找到该元素所继承的对象。但javascript中函数也为对象,而且还可以当做构造器,因此肯定会与普通的对象会有所区别,当我们使用typeof 来检测一个函数时,返回的不是object而是function。事实上,函数至少比不同对象多出两个属性constructorprototype。既然有区别,那么函数的__proto__是否存在,prototype又是干什么用的呢?普通对象的__proto__保存的到底是什么?来看下面的例子:

var obj0 ={};     //构造空对象
console.log(obj0.__proto__); //查看__proto__属性            
console.log(obj0.prototype);  //查看prototype属性           
console.log(typeof obj0);    //查看类型                     

var obj1 = {name:'likai'}; //构造另一个对象
console.log(obj1.__proto__); //查看__proto__属性

var obj2 = {age:22};     //好吧,再构造另一个对象
console.log(obj2.name);  //查看对象本身不存在的属性

obj2.__proto__ = obj1;  //嗯哼,注意,继承开始了
console.log(obj2.name);  //再次查看对象本身不存在的属性

function Obj(){       //搞个构造函数玩玩
  this.name='kai';
}
console.log(typeof Obj); //查看类型
console.log(Obj.prototype);  //查看prototype属性
console.log(Obj.__proto__); //查看__proto__属性
Obj.prototype.age = 22;   //来,再搞个继承看看,这个会成功么
Obj.__proto__.ages = 23;  //来,继续搞个继承看看,这个会成功么

console.log(Obj.prototype); //查看prototype属性
console.log(Obj.__proto__); //查看__proto__属性

var obj4 = new Obj();     //用构造函数来构造
console.log(obj4.age);    //看看有没有继承到
console.log(obj4.ages);    //看看有没有继承到
console.log(obj4.__proto__); //瞧瞧__proto__属性

function Objs(){      //另一个构造函数
  this.ages =24;
}
Obj.__proto__ = Objs;  //在来继承试试

console.log(Objs.prototype); //查看prototype属性
console.log(Objs.__proto__); //查看__proto__属性

var obj5 = new Obj();     //用构造函数来构造
console.log(obj5.age);   //看看有没有继承到age
console.log(obj5.ages);   //看看有没有继承到ages
console.log(obj5.__proto__); 

//最后,来点大逆不道的事
obj2.__proto__.addr="湖北孝感";  //在继承属性上加个地址属性
console.log(obj2.addr);  //有没有继承到
console.log(obj1.addr);  //父对象有没有改变呢

//千呼万唤的ages,来打个招呼吧
console.log(Obj.ages);


------结果如下-------
{}
undefined
object
{}
undefined
likai
function
{}
[Function: Empty]
{ age: 22 }
{ [Function: Empty] ages: 23 }
22
undefined
{ age: 22 }
{}
{ [Function: Empty] ages: 23 }
22
undefined
{ age: 22 }
湖北孝感
湖北孝感
23

从上面的例子,我们可以清楚的明白javascript是如何来实现继承的,对象在生成时就会有个__proto__属性包括构造函数,但普通的对象__proto__属性保存着对象,而函数保存着函数对象(暂且这样称呼吧)。噢,对了,再看了最后3行的结果之后,我们应该把保存着对象改为保存着对象的引用才对。直接生成的对象(此处应该也包括函数)应该是javascript内置的默认构造器行为,而函数构造器在构造对象的时候,会将属性prototype保存的内容复制到新产生对象的__proto__属性中,而构造函数自身的__proto__属性只标示自己作为对象时的父对象,并不会复制给新产生的对象。因此函数的prototype属性值和其产生的对象的__proto__属性值是一样的,至少在你手动改变之前是一样的。对象的__proto__属性以及函数对象的prototype属性构成了一条从下至上的对象引用链即为原型链

2.原型继承追本溯源

对象和函数对象产生的区别从1中的例子也看以看出一二,函数的__proto__显示[Function: Empty]
直接构造的对象的原型属性为空对象,即使构造的对象是空对象,在此我觉得称为根对象更好点,因为当我们在Object函数的原型属性上挂上值后,他就不为空了。不信么,看下面的例子吧:

var a = {};
var b={name: 'likai'};
console.log(a.__proto__);

Object.prototype.age = 2;
console.log(a.__proto__);
console.log(b.__proto__);
------结果如下-------
{}
{ age: 2 }
{ age: 2 }

显然可以认为,直接对象应该也会默认调用Object()来构造。我们说了,构造函数就是把自己prototype属性的值复制给创建的新对象,而函数也是一种对象,那他的默认prototype属性和__proto__属性值是什么呢?试试就知道了:

var a = {};
console.log(a.__proto__);

Object.prototype.age = 2;
console.log(a.__proto__);

function b(){};
console.log(b.prototype);
console.log(b.__proto__);
------结果如下-------
{}
{ age: 2 }
{}
[Function: Empty]

我们注意到prototype属性的值和普通对象的__proto__属性值一样,而函数对象的__proto__属性值却为 [Function: Empty],这让我们想起了和Object类似的东西–Function,他本身是个函数,可以用来创建函数对象,那么他的功能是否和Object类似呢,试试就知道了:

function a(){};
console.log(a.prototype);
console.log(a.__proto__);

Function.prototype.age = 2;
Function.__proto__.ages = 22;

console.log(a.prototype);
console.log(a.__proto__);
------结果如下-------
{}
[Function: Empty]
{}
{ [Function: Empty] age: 2, ages: 22 }

显然同样可以认为函数对象应该也会默认调用Function()来构造,但从结果看Function就比Object多了个[Function: Empty]Object也是个函数,既然函数默认由Function()来构造,而函数也属于一种对象,那么我们似乎可以用instanceof看看他们,来试试:

var a ={};
function b(){};
console.log(a instanceof Object);
console.log(b instanceof Object);

console.log(a instanceof Function);
console.log(b instanceof Function);

console.log(Function instanceof Object);
console.log(Object instanceof Function);
------结果如下-------
true
true
false
true
true
true

哇,这结果…ObjectFunction果然验证了函数也是对象这句话,但这你中有我,我中有你却让人摸不着头脑。就这样看肯定是不行的,这两个东东毕竟是Javascript本身提供的东西,看来得换一种思路了,问不了他们和他们儿子,去问他父亲去!来看看结果:

console.log(Function.prototype);
console.log(Function.__proto__);
console.log(Object.prototype);
console.log(Object.__proto__);
------结果如下-------
[Function: Empty]
[Function: Empty]
{}
[Function: Empty]

结果看着有三个都差不多的,继续来试试:

console.log(Function.prototype === Function.__proto__);
console.log(Function.prototype === Object.__proto__);

console.log(Object.prototype === Object.__proto__);
------结果如下-------
true
true
false

啊哈,从上面可以看到,ObjectFunction有着相同的__proto__属性值,而且Functionprototype__proto__属性值相同,根据上文所说的对象构建规则推测,那么ObjectFunction指向同一个父对象,结果显示为[Function: Empty],暂且喊他根函数对象吧。而我们的原型链,似乎到这里就到头了。两个内置的函数,一个负责产生普通对象,一个负责产生函数对象。前面说了一个根对象,现在又来一个根函数对象,那到底谁是根啊,不行,既然前面说了都可以看做是对象,那看来还得往上挖:

console.log(Function.__proto__.__proto__);
console.log(Function.__proto__.__proto__ === Object.prototype);
console.log(Function.__proto__.__proto__.__proto__);
console.log(Object.prototype.__proto__);
 ------结果如下-------
{}
true
null
null

看,空对象(根对象)出现了,从上面可以总结下:原型链的尽头就是根对象,根对象的__proto__值为null,虽然似乎Object可以看做是用Funciton构建的,但从前面的测试来看,绝不简单是这样,因为这是内置的东西,我们就不需要以Javascript的规则来看待,不然容易钻进牛角尖了。我们只需要记住Object的prototype代表这根对象就行。而想通过函数找到根对象,就需要使用__proto__属性向上找。因此我们可以做如下测试:

var a = {};
function b(){};
var c = new Function();

Object.prototype.age = 22;
console.log(Function.__proto__.__proto__);

console.log(a.__proto__);

console.log(b.__proto__ === Function.prototype);
console.log(c.__proto__ === Function.prototype);
console.log(b.__proto__.__proto__);
console.log(b.prototype === Object.prototype);

结果是显而易见的,就不多写了,需要注意的是普通函数的prototype属性值”{}“为空对象,而不是根对象。

3.原型继承学习总结

原型继承是javascript吸收自self语言的一个特色,通过稍微深入的挖掘,对其原理,特别是原型链有一个较为清晰的理解,而且原型链上都使用的是引用,也就是说在子对象中改变原型引用的值,也会对父对象造成影响,在这里我没有研究继承对子对象的影响,因为对大多数语言来说,继承对子类的影响都差不多的,特别是涉及到相同名字的属性都会造成覆盖效果,因此无需做过多的研究。

关于this绑定

在大多数oop语言中,this关键字用来代表当前对象(可以说这个词即容易理解又难以理解)。Jacascript中,在定义构造函数时,基本上都会用到this关键字,一般情况下定义属性还好,但当构造函内部继续定义函数的时候,很多时候对于构造函数内部的this值所代表的对象会产生混淆,在其内部函数想使用this关键字来引用当前对象的属性,当对此不了解的时候,就会发生难以理解的错误,原因就在于构造函数的内部函数使用的this关键字指向的并不是上文所说的当前对象,而是全局的宿主对象(node.js的root(global),浏览器的window)。

1.一句话搞定this绑定

谁(将)调用该方法,this就绑定到谁身上,没人就找全局宿主对象!来看:

    root.age = 33;
    function Constru(){
        this.age = 22;
        console.log(this.age);

        function innerFunc(){
            console.log(this.age);
        }
        innerFunc();

        this.printf = function(){

            function inMethodFunc(){
                console.log(this.age);
            }
            inMethodFunc();

            console.log(this.age);

      }
    }

    var obj = new Constru();
    obj.printf();
    ------结果如下-------
    22
    33
    33
    22

上面的例子中,按照谁调用,this就绑定到谁身上,第一个打印的this.age直接在构造函数下,一定是绑定到产生的对象上,obj.printf()内的this很显然绑定到obj对象上,而innerFuncinMethodFunc是直接调用的,所以就找到了全局宿主对象。

2.this绑定全局的解决方案

要解决上述问题,只需使用一个代替值保存正确的this引用,然后再使用代替值去表示当前对象:

     root.age = 33;
     function Constru(){
        var me  = this;  //用变量me来保存this绑定的对象
        this.age = 22;
        console.log(this.age);


        function innerFunc(){
            console.log(me.age);  //me.age
        }
        innerFunc();

        this.printf = function(){

            function inMethodFunc(){
                console.log(me.age);
            }
            inMethodFunc();

            console.log(this.age);

      }
    }

    var obj = new Constru();
    obj.printf();
    ------结果如下-------
    22
    22
    22
    22

3.this绑定总结

该错误在熟悉javascript之后就很少会犯,但有时候使用时却还是会疏忽而产生意外结果或错误,没什么难点,需要自己多注意。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值