这两天本小白在看一本书,书名《你不知道的js》听着很叼有木有。其实这已经是本人第二次看这本书,这本书全书不足200页,但是却真的是值得一读再读的好书,再次强烈推荐下,同推的还有《js忍者秘籍》是jQuery作者写的,也很好,需要一定的基础才能看!当然也不需要太深的基础否则本小白怎么看呵呵哒。闲话少述下面放一段书上的代码,就是这段代码让我今天有不小的收获(其实这个也是作者从mdn上copy的,所以说看文档真的重要!!!重要的话打三个感叹号!!!)
首先我相信大家如果一些书的话对js中的this绑定应该还算熟悉吧有四种绑定,默认,隐式(作为方法调用),显式(call,apply),new(new调用),那大家有想过这些绑定的优先问题吗?比如同时出现new绑定和显式绑定,到底函数中的this绑定到哪呢?(因为new和call或者apply是无法直接一起使用的,所以无法通过new foo.call(context)来直接测试,但是咱们可以使用bind来看看)相信大多数人和我一样根本没有想过这个问题,今天大家不妨跟着我来看看这个问题的答案。
if (!Function.prototype.bind) { Function.prototype.bind = function(context) { if (typeof this !== 'function') { throw new TypeError('只有函数才可以调用bind函数'); } var args = Array.prototype.slice.call(arguments, 1), toBind = this, NOP = function(){}, bound = function () { return toBind.apply( (this instanceof NOP && context ? this : context), args.concat(Array.prototype.slice.call(arguments))); }; NOP.prototype = toBind.prototype; bound.prototype = new NOP; return bound; } }
上面的代码是对ES5新增内置函数bind的实现,出现在讲解this绑定优先级的一章。如果大家和我水平相近应该对上面代码还算是hold住,比较迷茫的就应该是
this instanceof NOP && context ? this : context,这句代码的意义是什么呢?其实,这段代码就是检验你对这个bind函数返回的函数的调用方式,如果你是直接调用或者你把它作为一个对象方法调用,这两种情况都是返回 toBind.apply(context, arguments),这也是大家所期望的对吧?毕竟咱们调用啦bind给函数绑定了一个指定的上下文。可是大家思考下,如果你是把返回的这个bound(当然我这里这样说都是偷懒的说法,大家一般都要先把fn.bind(context)的返回值赋值到一个变量,我为了方便就直接说上面代码中的bound啦,但是注意如果直接在全局调用bound肯定是会出问题的啦!),用做构造函数调用呢?也就是var obj = new bound()?那各位看官你们说,这个函数中的this到底绑定的是什么呢?为了解决这个问题咱们先快速回顾下用new操作符调用一个函数时,会发生什么?
1.创建一个空对象
2.设置它的[[prototype]]属性,将其连接到函数的prototype对象上(也就是委托)
3将这个新创建的对象绑定到函数内部的this上(然后就是执行这个函数的代码)
4如果函数不返回一个对象(注意这个对象包括函数啊,数组啊这些),就返回this
所以说现在如果用new操作符调用bound的话,首先咱们要创建一个空对象,然后设置原型链,好现在咱们看bound函数的内部,那句this instanceof NOP && context ? this : context在这个情况下得到的值便是context啦因为此时this instanceof NOP为true(写到这里作者我要吐槽下线,其实写这个文章的时候我才发现那本书的作者已经有点跑题了,他本意用这段代码是要探讨到底new绑定和call或者apply绑定到底哪个优先级高,可是他其实在这里偷换了概念,这里先提一下,怎么个换概念咱们后面说,这段代码其实本来给我最大的帮助也不是什么this优先级的问题,而是js最根本的原型问题。我本人觉得其实this这个并没有太大意义)。我在看到this instanceof NOP的时候有点发懵,为什么这个instanceof返回true呢?我开始想了下,以为是因为我是建立了一个空对象,而NOP是个空函数,所以就为true?现在看当时的想法真是哔了整个动物园了,这种想法简直是一点都不理解js和核心原型这一概念的最佳体现啊。现在咱们来看看到底是怎么个情况(当然鉴于本人还是小白说不定也是错的,大家一起讨论,请高手指正!)。
先提个问题instanceof这个运算符到底是怎么工作呢?如果大家有类和实例的概念,可能会说就是检查这个对象是不是这个类的实例啊(正如它的名字暗示的意义),可是有一句会应该是学过js的人都知道,js是没有类的,它的继承原理是原型!所以这个instanceof肯定不是像它名字这个作用原理(真是坑爹啊名字都不起好)。其实instanceof是通过判断前面这个对象的原型链中有没有出现过后面这个函数.prototype对象(或者说这个对象有没有委托给函数.prototype对象,本人更爱这个说法,可以帮助我彻底把和类的说法区分开,防止混淆),如果有则为true,否则false,现在来看bind中的这两句代码,也是为何当用new调用函数的时候this instanceof NOP为true的原因。
NOP.prototype = toBind.prototype;
bound.prototype = new NOP;
这段代码先是将NOP.prototype设置为调用bind方法的函数的prototype对象,之后又将bound.prototype委托到了一个新的空对象上(new NOP),而这个对象又委托到NOP.prototype上(其实很多书上都会写成将原型设置为一个NOP的实例,可是我觉得这样没有任何好处,反而会让大家无法看清楚js中原型的真面目而且越发混淆,事实上这段代码给我带来的帮助也是帮助我把之前的知识梳理了下,对原型的认识深刻了点,所以我会尽量避免使用任何可能与传统类语法混淆的词汇,希望大家可以接受委托这个词,其实这个词本身也很形象也很好理解啊,是另一种方式的继承,你想想看,你没钱的时候是不是找你老爹要呢?而你本来就是被委托到你老爹那了啊,同样的咱们还被委托到了妈妈那,所以饿了可以找妈妈给你做饭吃,如果你没有被委托到某位那边,人家就不会鸟你,也不会让你用他的东西,也就是不可以访问他的属性和方法!!!),所以在函数内部的this此时是一个被委托到NOP.prototype上的对象所以返回了true(但是要注意这个对象并不是直接被委托到NOP.prototype,中间还有个new NOP)。
原谅本小白初出茅庐本身文笔也不佳,加之并无多少干货,不知所云了半天好像也没有说出什么东西来,唉,台下的少侠你先把鸡蛋收起来好不好。。。。希望这篇文章对大家区分开原型和类这两个极易混淆的概念提供一点点帮助,最主要的是认识到instanceof到底是怎工作的,大家以后不妨多想想委托这个词,以后会介绍基于原型的继承方式,大家会看到这个比模仿类继承要简介明白的多!!!最后再说下为什么说书的作者偷换概念,书中是说比如var fn = foo.bind(obj1),(obj1是一个定义的对象,foo是一个函数),那么用new调用fn,则fn里面的this指向的不再是obj1而是那个新创造的对象,比如var b = new fn,就是这个b,大家可以测试下确实如此,然后作者就得出了new绑定优先于显式绑定,其实完全不在理我觉得,可以改变绑定的原因全在于
(this instanceof NOP && context ? this : context), args.concat(Array.prototype.slice.call(arguments))); }; NOP.prototype = toBind.prototype; bound.prototype = new NOP;
这三行代码,如果咱们用最简单的实现显示绑定的函数function test (fn, o){return function(){fn.apply(o, arguments)};},然后再去测试会发现并不像他说的什么new优先于显式绑定,关于这个绑定,我会在下一篇的瞎BB几句,如果你看到了这里,说明你的水平不太高和我差不多,那你还不赶紧关注我,看看我遇到什么问题,然后帮你排排雷!!!欢迎大家和我一起讨论学习!!!