资讯专栏INFORMATION COLUMN

深入JavaScript(一)this & Prototype

The question / 1256人阅读

摘要:然而事实上并不是。函数本身也是一个对象,但是给这个对象添加属性并不能影响。一图胜千言作者给出的解决方案,没有麻烦的,没有虚伪的,没有混淆视线的,原型链连接不再赤裸裸。所以是这样的一个函数以为构造函数,为原型。

注意:本文章是个人《You Don’t Know JS》的读书笔记。
在看backbone源码的时候看到这么一小段,看上去很小,其实忽略了也没有太大理解的问题。但是不知道为什么,我觉得心里很难受。所以我觉得一定要真正解决这个问题。这个问题就是原型。
就是下面这段代码:

var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;

看懂了这一段简单的代码了吗?

其实就着backbone的注释,理解完全没有问题。但是之后我不小心深究了一下,突然发现自己对于这一个问题的理解还很不透彻。很惭愧,写了好一阵子的JavaScript,但是一直都是以实用主义的角度来写的。很多时候真的把JavaScript当成传统有class的语言来写了。但是JavaScript是一门很“狡猾”的语言,因为它的很多关键字仿佛都在透露自己是传统面向对象语言,instanceconstructornew等等。然而事实上并不是。解决这个问题的方法就是读书,我选了《You Don’t Know JS》。我快速过了相关的那一本,被这本书深深震撼到。本文是个人的学习笔记,也希望能帮助到你。

this

this是函数执行的时候,内部产生的一个内部对象。在情况复杂的时候,this的指向会比较难把握。
this的指向常常不明,在书中主要列举了两种情况的混淆和四条规则,掌握了这两者,this就会比较明晰了。(还有很多很复杂的情况需要细致的分析)

两种混淆

1,this不是函数对象本身。函数本身也是一个对象,但是给这个对象添加属性并不能影响this。除非foo.call(foo, i),这句代码的含义是:在foo对象上调用foo函数,并传递参数i。这是一个特殊情况。
2,this不是函数内部的作用域。因此在函数内部申明的变量如果不加其他操作,和this根本不会有任何关系。

四条规则

这四条规则只有一个中心,就是call-site。本质上就是指this决定于调用的对象而不是在哪里声明。
1,直接调用函数。函数内部this会指向全局。this此时为window闭包调用亦是如此。
2,内部绑定调用。如obj.foo()。此时foo中的this指的就是obj对象。注意obj.foo(),不管前面是不是还有其他调用,但是都不会改变this此时为obj
3,直接绑定调用。主要是用了callapply来调用。除此之外还有先bind在调用的方式,这种方式非常强。直接绑定调用的this就是指定的那个参数的对象。
4,new方式调用var bar = new foo() 这一句代码的作用之一就是把foo函数中的this全部赋到bar上作为新的bar对象的属性。

优先级

1,一般来说直接绑定调用会比内部绑定调用有更高优先级。
2,new在一定情况下能“覆盖”bind调用。用这个特性可以实现函数currying化。(函数传入多个参数,在bind的时候传入固定的变量)

function foo(val) {
    this.val = val;
}
var obj = {};
var bar = foo.bind(obj);
bar("p1");
var baz = new bar("p2")
console.log(baz.val); // p2
new & Object.create

JavaScript没有类,也不能创建实例。但是语法上确实非常非常面向对象,让人误解。new就是总被误解。

var bar = new foo();

下面详述这一句代码执行的效果。这非常重要。
1,执行foo()函数。是“构造方式调用”。注意,JavaScript里面并没有真正的构造函数。
2,连接foo.prototype和新对象bar[[prototype]]。(相当于把foo.prototype整体搬到(引用)bar.__proto__里面)
3,把foo中的内部对象this的属性给bar
4,foo如果有返回对象就返回那个对象,没有就自动返回一个新的对象。对象特征如2,3所述。
事实上,new做得事太多,很多时候令人讨厌。
注意:barfoo函数对象本身的属性没有关系。

在经典的教材上,JavaScript的“继承”常常使用下面的代码:

Bar.prototype = new Foo();

这句代码很经典,但是并不好。它固然能够完成原型链的连接,但是也产生了很多不必要的麻烦。Bar.prototype上会带上Foothis,副作用大。因此书上建议是这样做来完成继承的:

Bar.prototype = Object.create(Foo.prototype);

A = Object.create(B)的作用是把B对象的属性添加到A__proto__上面,然后A[[prototype]]B的连接。有个比较“直观”的说法,就是把B的“属性”,添加到A.__proto__上面,然后把B.__proto__也添加进去(原型链延长的不严谨说法)。试着在chrome里面console一下,就会发现此时的A只有一个__proto__属性,里面是B的属性和B.__proto__。这样就很好地完成了“继承”,而且也没有副作用了。下面提供一个降级写法(原理很简单。按照之前的几条规则可以很轻易地读懂):

// Object.create
if(!Object.create) {
    Object.create = function(o) {
        function F(){};
        F.prototype = o;
        return new F();
    }
}

还需要注意的是constructor属性。如果对象是函数,这个属性会自动出现在函数的prototype里面,但有时候会被覆盖。

原型

prototype是函数对象的一个属性。其实可以理解成比较特殊的一个属性,一个对象。这个属性在用构造器方式调用(new)的时候就用来连接新对象。连接到最后就是Object.prototype,这是每一个对象(不包括null)中__proto__的尽头,原型链的尽头。可以想象,对象的声明是new Object(),那么新的对象原型链自然就会有Object.prototype了。如果想摆脱这个Object.prototype,可以用Object.create(null)
经常有这种情况,调用一个对象的属性的时候,如果在本身找不到就会沿着原型链找。直观而不严谨地说,就是从__proto__一直找下去。这也揭露了a instanceof foo的本质。就是沿着原型链找,看看在a的原型链上有没有foo.prototype的存在,有就返回trueinstanceof这个英文很误导,因为JavaScript里面根本上就算不上有实例这样的东西。

OLOO

作者对于JavaScript的面向对象有一个非常惊艳的解决方案,在那之前先看看以前的方案是怎么做的。

function Foo(who) {
    this.me = who;
}
Foo.prototype.identify = function() {
    return "I am " + this.me;
};

function Bar(who) {
    Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};

var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );

b1.speak();
b2.speak();

一图胜千言:

作者给出的解决方案OLOOobjects-linked-to-other-objects),没有麻烦的new,没有虚伪的constructor,没有混淆视线的call, apply, bind,原型链连接不再赤裸裸。感觉真心很棒(注意一点,JavaScript对象的方法其实是不是“属于”一个对象值得商榷,因为所谓方法其实和这个对象关系并没有想象中大):

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create( Foo );

Bar.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};

var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );

b1.speak();
b2.speak();

仍然是一图胜千言:

最后来讲讲这个:

var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;

很好懂了吧。构造形式调用Surrogate函数,把this上的属性交给child.prototype,这里是constructor;把Surrogate.prototype的内容搬到child.prototype.__proto__上面。而Surrogate.prototype引用自parent.prototype。所以child是这样的一个函数:以child为构造函数,parent.prototype为原型。

总结:

这个问题是老生常谈的问题了,但是我觉得对于每一个写JavaScript的人来说,这个问题是永远避不开的。最后再次推荐《You Don’t Know JS》这一套开源书。可以毫不夸张地说,这是自红宝书和犀牛书之后讲JavaScript的最好书了。深度深得恰到好处,语言和例子清晰简明,我还要继续学习。

有关 this & Object Prototypes ,书的内容远远比我这篇文章丰富精彩,去读吧。(我也要复反复读)

如果有任何错误请轻喷,相互学习~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80269.html

相关文章

  • 《你不知道的javascript》笔记_对象&原型

    摘要:上一篇你不知道的笔记写在前面这是年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响年是向上的一年在新的城市稳定连续坚持健身三个月早睡早起游戏时间大大缩减,学会生活。 上一篇:《你不知道的javascript》笔记_this 写在前面 这是2019年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响2018年是向上的一年:在新的城市稳定、...

    seasonley 评论0 收藏0
  • javascript中的constructor&&prototype

    摘要:于是退而求其次叫为类的构造函数。如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数。遇到的问题始终指向创建当前对象的构造函数。 Object.constructor,prototype 对象的prototype和constructor是两个重要的属性,他们总是成对出现,提到constructor的地方,不得不涉及到另外一个非常重要的属性prototype,它是js中基于...

    huaixiaoz 评论0 收藏0
  • 温故js系列(15)-原型&原型链&原型继承

    摘要:给添加属性给的原型对象添加属性原型链在中,每个对象都有一个属性,其保存着的地址就构成了对象的原型链。实例变量实例函数原型链继承有了原型链,就可以借助原型链实现继承。是中唯一一个处理属性但是不查找原型链的函数。 前端学习:教程&开发模块化/规范化/工程化/优化&工具/调试&值得关注的博客/Git&面试-前端资源汇总 欢迎提issues斧正:原型&原型链&原型继承 JavaScript-原...

    Ethan815 评论0 收藏0
  • JavaScript - 原型&原型链

    摘要:可以看出,这个查找过程是一个链式的查找,每个对象都有一个到它自身原型对象的链接,这些链接组件的整个链条就是原型链。原型的构建字面量方式当通过字面量方式创建对象时,它的原型就是。 面向对象 JavaScript没有类(class)的概念的(ES6 中的class也只不过是语法糖,并非真正意义上的类),而在JavaScript中,在 JavaScript 中,除了 String, Numb...

    wean 评论0 收藏0
  • 图解javascript原型&原型链

    我们在学习javascript时,经常会听到万物皆对象,但是呢,其实万物皆对象的对象也有区别。分为普通对象和函数对象。1.对象分为函数对象和普通对象    通过new Function()创建的对象都是函数对象,其他的都是普通对象。showImg(https://segmentfault.com/img/bVbtWre?w=526&h=252); 2.构造函数而提到new关键字,我们不得不提到构造...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<