Posts tagged with “Javascript”

JavaScript中七种函数调用方式及对应 this 的含义

旧文重发存档,原文发表于9年前。原文地址

this 在 JavaScript 开发中占有相当重要的地位,不过很多人对this这个东西都感觉到琢磨不透。要真正理解JavaScript的函数机制,就非常有必要搞清楚this到底是怎么回事。

函数调用方式不同,this 含义也跟着不同。JavaScript语言中有七种调用函数方式:

第一种:调用方法

var obj = {
    method: function() { alert(this === obj); }
}
obj.method();

上面这行obj.method()显然method是作为方法被调用,这种情况下,函数体中的this绑定的就是method的宿主对象,也就是obj。 从这种调用方式我们得出第一定律: 第一定律:以方法方式调用函数,this则绑定宿主对象。

第二种:调用全局函数

var method = function(){alert(this === window);}
method();

上面这个函数是个全局函数。我们知道,全局变量或函数都相当于window对象的属性。也就是说,上面这句实际上等同于下面这句:

window.method = function(){alert(this === window);}
window.method();

既然这样,那么这里 this 绑定到 window 对象就显而易见,很容易理解了。相当于方法调用模式的一个特例,并不违背第一定律。

第三种:全局函数内调用内部函数

var m_ext = function() {
    alert(this === window);
    var m_inner = function() {
       alert(this === window);
    }
   m_inner();
}
m_ext();

执行上面这段代码,你会惊讶的发现,两个布尔表达式的值都是真。也就是说子函数孙函数,只要是以函数的方式调用,this 就铁了心绑定 window 对象了。从这种调用方式我们得出第二定律: 第二定律:不论子函数孙函数,只要是以函数的方式调用,this 就铁了心绑定 window 对象。

第四种:方法内调用内部函数

var obj = {};
obj.method = function() {
    alert(this === obj);
    var m_inner = function() {
       alert(this === window);
    }
   m_inner();
}
obj.method();

执行上面这段代码,第一个this绑定到obj你不奇怪,第二个this绑定到window其实也不奇怪。它仍然遵守第二定律,也就是不论子函数孙函数,只要是以函数的方式调用,this 就铁了心绑定 window 对象。

第五种:作为构造函数调用

function Person(name, age){
    this.name = name;
    this.age = age;
}
var john = new Person('John', 38);
alert(JSON.stringify(john));

你会说,哇,这很眼熟。生成某个类的新对象啊。Javascript就是这么另类,函数确实可以这么调。 这次弹出的是这样一个字符串:{"name":"John","age":38}。哇,我明明没在函数定义里写return 语句,它却主动返回了一个对象给我。没错,即便你在函数定义里return某个东西,它也不会理你。(注意:如果你return一个function就会有惊喜,不信你试试看)。从这种调用方式我们得出第三定律:

第三定律:如果在一个函数本身没有返回值,在其前面加上 new 调用,就会创建一个连接到该函数 prototype 属性的新对象,再把 this 绑定到该对象,然后执行该函数,最后返回该对象。如果该函数有返回值,且返回值为字符串/数字/布尔值等简单对象的话,该返回值会被丢弃。但如果该函数的返回值为对象,函数或者数组等复杂对象的话,该函数则会返回该返回值,抛弃this绑定的对象。据我测试,如果返回值是一个函数时,该函数会返回undefined。我暂时还不知道为什么会这样。

第六种:用函数对象的apply方法调用

var my_concat = function(stra, strb){
   alert(this);
    return stra + '' + strb;
}
var param = ['hello', ' world']
alert(my_concat.apply('bullshit', param));

你一定注意了,在这里 this 绑定的是apply方法的第一个参数 'bullshit'。从这里我们得出第四定律: 第四定律:用方法的apply/call方法调用方法时,this 则被绑定到apply/call方法的第一个参数。 想谁就是谁,嗯,吴妈也行。JavaScript的程序员们好幸福哦。

第七种:用函数对象的call方法调用

var my_concat = function(stra, strb){
   alert(this);
    return stra + '' + strb;
}
alert(my_concat.call('bullshit', 'hello',' world'));

对啦,你或许会多问一句,apply 的第二个参数有什么规定?call方法和apply又有什么不同?嗯 ,真是勤学好问,我就再啰嗦一下说说apply和call:

apply 和 call 的区别:

apply要求第二个参数必须是数组。否则就会报 TypeError: second argument to Function.prototype.apply must be an array. 而call则没这么严格啦,第二个参数要什么类型?随意啦,还可以有第三个第四个第五个第六个....啦。

我想我说的够清楚了。感谢《JavaScript The Good Parts》和《JavaScript Definitive Guide》,要不然我也弄不明白呢!

status code versus error code

同事扔给我一张截图,显示errCode是5,说:

「已注册」的error code是5, errorCode 4开头的会到404页面, 5开头的会到505页面,
我们之前有通过的都是200

不不不
这个和 statusCode 是两码事儿
errCode 是业务错误代码
20x 40x 50x 是返回响应的状态码,通常是框架自动给的,若你的请求参数没有问题,系统通常都会返回状态码 200。但返回200并不意味着一切都对,在我们的项目中,状态码返回 200 并且 success 为 true 或者 errCode = 0才是真的没有问题

同事

哦,是这样,所以我们还是有statusCode

如果参数根本不符合约定,系统会立即拒绝你,那时候statusCode 会是 40x
如果参数都对,但后端的代码出了bug造成服务器不能正常返回,response会报 50x 表示前端没错,但服务器那边出问题了。
换言之如果是 20x,则你没错,我也没错,成功没有成功是业务逻辑的事情。
如果是 40x 意味着前端送过来的数据不符合后端要求,因此没有被进一步处理,而是被直接拒绝了
50x 表示前端送过来的东西没有大问题,但后端没有处理好出错了

同事

好的,那我明白了,我记错了,是statusCode才是指到505,404, 我以为都改成了errCode

所以 errCode 5 一定不能一杆子指到 505 页面去。
发现 50x 了去找后端工程师的麻烦一般不会错,如果 40x 了去找后端之前,最好先检查下自己的参数和出错信息

同事

status code 和 errCode 本质上是两个东西,errCode是返回数据的一部分
status code 是本次请求返回响应的状态码,可以理解为和返回数据是同一等级的

同事

那我们的error.response的数据结构有改变吗,我现在还是可以抓取到error.response.status,说明这部分应该是没有改的

这个是axios的约定,并不是后端能够决定的,我们结构的变化,只是response.data 结构的变化是 response.data 吧,我不是很确定,毕竟好久不写 axios 代码了

同事

是的,但是后来有加了一个data, 我们现在的结构变成这样的 [ Photo ]

对。后端只给了一个data键,图里的第一个data是 axios给的,需要注意的是,不同的框架给返回数据的键取的名字可能会不一样。如果不用axios而是用别的库,数据那一级的键的名字就很有可能不叫 data

同事

嗯嗯

话外音

我相信她这次真的搞明白了。哈。聊天记录稍加编辑就水了一篇博客,不过我相信它应该会对前端新人有所帮助。

Defining arrow functions at a wrong place caused *this* object becoming undefined.

Today I was caught by a bug. I got an error message like "Uncaught (in promise) TypeError: Cannot set property 'users' of undefined" in the following code:

    import axios from 'axios';
    export default {
      name: "Users",
      data() {
          return {
            users: null
          };
      },
      created: () =>  {
        axios
          .get('https://jsonplaceholder.typicode.com/users')
          .then(res => {
            this.users = res.data;
          })
      }
    }

Finally, I figured it out by changing the created method definition from an arrow function to a normal function, the following is the correct code:

    import axios from 'axios';
    export default {
      name: "Users",
      data() {
          return {
            users: null
          };
      },
      created() {
        axios
          .get('https://jsonplaceholder.typicode.com/users')
          .then(res => {
            this.users = res.data;
          })
      }
    }

Do you see the difference?