浅谈 js 函数调用

本文是作者在重读 javascript 权威指南函数调用部分的时候的一个笔记, 算下来大概一半是书上的话, 一半是自己的理解再配上一些例子来加深印象, 分享给大家。

正文从这里开始。

函数调用

构成函数主体的 js 代码在定义的时候是不会执行的, 只有在调用该函数的时候它们才会执行。

一共有四种方式来调用 js 函数

  • 作为函数
  • 作为方法
  • 作为构造函数
  • 通过它们的 callapply 来进行间接调用

函数调用

使用调用表达式可以进行普通的函数调用也可以进行方法调用。

对于普通的函数调用, 函数的返回值就是调用表达式的值。如果该函数返回是因为解释器达到了结尾, 返回值就是 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 会指向全局变量, 而全局变量上面是没有 resolvereject 方法的, 我们的本意是想要通过 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] 等挂在了每个实例上。

间接调用

间接调用中其实就只有两个方法, callapply

两个方法都允许显式的指定 this 的取值, 也就是说, 所有的函数都可以作为任何对象的方法阿莱调用, 哪怕这个函数不是那个对象的方法。

我们在看到一些库的 this 非常强大的时候, 可能就是在实现的时候, 用 callapply 强行指定的...

原文地址 : http://www.amnhh.xyz/2017/02/07/浅谈函数调用/

原文地址:https://www.cnblogs.com/amnhhforever/p/6386725.html