本文是作者在重读 javascript 权威指南函数调用部分的时候的一个笔记, 算下来大概一半是书上的话, 一半是自己的理解再配上一些例子来加深印象, 分享给大家。
正文从这里开始。
函数调用
构成函数主体的 js
代码在定义的时候是不会执行的, 只有在调用该函数的时候它们才会执行。
一共有四种方式来调用 js
函数
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的
call
和apply
来进行间接调用
函数调用
使用调用表达式可以进行普通的函数调用也可以进行方法调用。
对于普通的函数调用, 函数的返回值就是调用表达式的值。如果该函数返回是因为解释器达到了结尾, 返回值就是 undefined
。 如果函数返回是因为解释器执行到一条 return
语句, 则返回至就是 return
会后的表达式的值, 如果 return
语句没有值, 则返回 undefined
。
方法调用
一个方法无非就是个保存在一个对象的属性里的 js
函数。 如果有一个函数 func
和一个对象 obj
, 我们就可以为 obj
定义一个名为 method
的方法 :
obj.method = func;
调用的时候就类似这样 :
obj.method();
在方法调用中, 函数表达是本身其实就是一个属性访问表达式, 只不过这个属性访问表达式最终取到的是函数的引用而非是一个具体的值。
对方法调用的参数和返回值的处理, 和上面描述的普通函数调用完全一致。
方法调用和函数调用有一个重要的区别, 就是调用上下文。在刚刚的 obj.method()
里面, 函数的 context
会变为 obj
, 所以在函数内部可以通过 this
来取到 obj
的引用。
方法和 this
关键字是面向对象编程范例的核心。任何函数只要作为方法调用实际上都会传入一个隐式的实参, 这个实参实际上是一个对象, 方法调用的母体就是这个对象。
需要注意的就是, this
是一个关键字, 不是变量也不是属性名, 而且 js
的语法也不允许给 this
赋值, 但是可以预存 this
, 比如我们在函数内部经常会这样 :
function Promise (func) {
var resolve = function (val) {
this.resolve(val);
};
var reject = function (val) {
this.reject(val);
};
}
Promise.prototype.resolve = function () {};
Promise.prototype.reject = function () {};
接下来我们使用 var pms = new Promise(func)
来搞一个实例, 会惊奇的发现报错了.. 因为此时的 this
会指向全局变量, 而全局变量上面是没有 resolve
和 reject
方法的, 我们的本意是想要通过 this
来拿到实例 pms
的引用, 进而从 pms
上去找到 Promise.prototype.resolve
, 这个时候我们就需要把 this
的值预存一下... 就类似这种 :
function Promise (func) {
var me = this;
var resolve = function (val) {
me.resolve(val);
};
var reject = function (val) {
me.reject(val);
};
}
Promise.prototype.resolve = function () {};
Promise.prototype.reject = function () {};
这样就可以保证在实例化的时候, resolve
函数中的那个 me
指向了实例, 至于这个 Promise
的实现, 具体参考了这里 https://github.com/hanan198501/promise, 当然这不是本文要说的, 只是意在讲预存 this
的重要性
方法链
其实这里就涉及到原来有看过源码的 jQuery
的链式调用的核心了
当方法不需要返回值的时候, 最好直接返回 this
, 如果在设计 API
的时候一直采用这个方式, 就可以构成一种链式调用。
构造函数调用
如果函数或者方法调用之前带有关键字 new
, 它就构成了构造函数调用。这里其实说明一件事情就说明了整个过程了。
...所以下面就说一下 var person = new Person()
到底发生了什么...
- 创建一个新的空对象
- 完成内部 [[prototype]] 的指向绑定
- 把
Person
内部的this
全部指向新生成的对象 - 最后检测
Person
内部有没有return
一个对象, 如果 return的是一个对象, 则调用表达式的值就是这个对象, 没有
return或者
return` 的是一个原始值的话, 则调用表达式的结果就是这个新生成的对象
这里会对第二条和第三条特殊的讲一下 :
首先是第二条 : 完成内部 [[prototype]] 的绑定
其实我们打开 chrome 控制台, 敲下 {}
按回车, 点开生成的那个东西, 会有一个 __proto__
的东西, 这个东西就是内部 [[prototype]] 在浏览器里的实现, 在 es6
里虽然没有写入正文, 但也写入了附录, 所以可以认为是新的标准, 在所有浏览器(包括 IE11) 也都部署了这个属性(__proto__
的描述来自于阮一峰 ECMAScript 6 入门, 传送门 :
http://es6.ruanyifeng.com/#docs/object#proto属性,Object-setPrototypeOf,Object-getPrototypeOf)。
而第二条就是做了这一个指向, 会把实例 person
内部的 [[prototype]] (我还是更喜欢叫 __proto__
来着...) 来指向 Person.prototype
, 其实也就是这条句子所表明的那样 :
person.__proto__ === person.constructor.prototype;
当然明眼人有看的出来... 其实 person.constructor
就是 Person
, 当然 constructor
不是这里要讲的了, 只是用到了....
接下来是第三条 : 把 Person
内部的 this
全部指向新生成的对象
这里的意思其实是, 比如 Person
内部会有很多东西 :
function Person () {
this.name = 'anning';
this.age = 22;
}
在调用 var person = new Person()
的时候, 在 Person
内部的 this
指向了新生成的对象, 也就是执行了 :
person.name = 'anning';
person.age = 22;
这一点在很多地方其实都有使用, 比如 jQuery
中对 jQuery.fn.init
构造函数的实现, 就是在 init
函数里面大量使用了 this
, 在 this
上挂非继承属性, 最后在生成 jQuery.fn.init
的实例的时候, 使实例的非继承属性, 如 context
属性, 整型属性 [0]
等挂在了每个实例上。
间接调用
间接调用中其实就只有两个方法, call
和 apply
。
两个方法都允许显式的指定 this
的取值, 也就是说, 所有的函数都可以作为任何对象的方法阿莱调用, 哪怕这个函数不是那个对象的方法。
我们在看到一些库的 this
非常强大的时候, 可能就是在实现的时候, 用 call
和 apply
强行指定的...
原文地址 : http://www.amnhh.xyz/2017/02/07/%E6%B5%85%E8%B0%88%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8/