Javascript中神奇的this_javascript技巧

Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑。

1. 误以为 this 指向函数自身

根据 this 的英语语法,很容易将函数中出现的 this 理解为函数自身。在 javascript 当中函数作为一等公民,确实可以在调用的时候将属性值存储起来。但是如果使用方法不对,就会发生与实际预期不一致的情况。具体情况,请看下面代码

  function fn(num){
    this.count++;
  }

  fn.count = 0;

  for(var i=0;i<3;i++){
    fn(i);
  }
  console.log(fn.count); // 0

如果 fn 函数里面的 this 指向自身函数,那么 count 属性的属性值就应该产生变化,但实际上却是纹丝不动。对于这个问题,有些人会利用作用域来解决,比如这么写

  var data = {
    count:0
  };

  function fn(num){
    data.count++;
  }

  for(var i=0;i<3;i++){
    fn(i);
  }

  console.log(data.count);  //3

又或者更直接的这么写

  function fn(num){
    fn.count++;
  }

  fn.count = 0;

  for(var i=0;i<3;i++){
    fn(i);
  }

  console.log(fn.count);//3

虽然这两种方式都输出了正确的结果,但是却避开了 this 到底绑定在哪里的问题。如果对一个事物的工作原理不清晰,就往往会产生头痛治头,脚痛治脚的问题,从而导致代码变得的丑陋,而且维护性也会变得很差。

2. this神奇的绑定规则

2.1 默认绑定规则

第一种是最常见的 this 的绑定,看一下下面的代码

  function fn(){
    console.log(window === this); //浏览器环境
  }
  fn(); //true

函数fn 是直接在全局作用域下调用的,没有带其他任何修饰,这种情况下,函数调用的时候使用了 this 的默认绑定,指向了全局对象。

这样就清楚了第一个例子中的 this 指向, fn 函数中的 this 指向了全局变量,所以 this.count++ 相当于 window.count++(浏览器环境下),当然不会对 fn 函数的count属性产生影响。

有一点要说明的是,上面种情况只能在非严格模式(strict mode)下才能发生,在严格模式下,会将 this 默认绑定为 undefined。以避免全局变量的污染。

2.2 隐式绑定规则

如果函数在以对象为上下文进行调用,那么 this 的绑定就会产生变化。this 会绑定到调用这个函数的对象,查看下面代码:

  var obj = {
    a:1,
    fn:function(){
      console.log(this.a);
    }
  }

  obj.fn(); //1

即使函数声明不在对象当中,this 指向仍会产生变化

  function fn(){
    console.log(this.a);
  }
  var obj = {
    a:1,
    fn:fn
  }
  obj.fn(); //1

由此可见,this 的绑定,不与函数定义的位置有关,而是与调用者和调用方式有关。

在隐式的绑定规则下,有一些特殊的地方,需要注意。

2.2.1 多层对象调用 this 的指向

  function fn(){
    console.log(this.a);
  }
  var obj = {
    a:1,
    obj2:obj2
  }
  var obj2 = {
    a:2,
    obj3:obj3
  }
  var obj3 = {
    a:3,
    fn:fn
  }

  obj.obj2.obj3.fn(); //3

在多层对象引用下,this 指向的是调用的函数的那个对象。

2.2.2 隐式赋值可能存在丢失现象

查看下面代码

  function fn(){
    console.log(this);
  }
  var  obj = {
    fn:fn
  }

  var fun = obj.fn;
  fun(); //window

虽然 fn 引用了 obj.fun ,但是函数的调用方式,仍是不带任何修饰的,所以 this 还是绑定在了 window 上。
还有一种情况,容易让大家忽略,那就是传参的时候,其实会进行隐式赋值。

 function fn(){
    console.log(this);
  }

  function doFn(fn){
    fn();
  }

  var obj = {
    fn:fn
  }

  doFn(obj.fn); //window

隐式绑定 this 不是一种很推荐的方式,因为很有可能就发生丢失的情况,如果业务当中对 this 的绑定有要求,建议还是使用显示绑定的方式。

2.3 显式绑定规则

显示绑定就是利用函数原型上的 apply 与 call 方法来对 this 进行绑定。用法就是把想要绑定的对象作为第一个参数传进去。

  function fn(){
    console.log(this);
  }

  var obj = {};

  fn.call(obj); //{}

有些时候会想将函数的 this 绑定在某个对象上,但是不需要立即调用,这样的话,直接利用 call 或者 apply 是无法做的。

  function fn(){
    console.log(this);
  }

  function bind(fn){
    fn();
  }

  var obj = {
    fn:fn
  }

  bind.call(obj,fn); //window

上面这个例子,看似好像可以,但实际上是 bind 函数的 this 绑定到了 obj 这个对象,但是 fn 仍然是没有任何修饰的调用,所以 fn 仍然是默认的绑定方式。

  function fn(){
    console.log(this);
  }

  function bind(fn,obj){
    return function(){
      fn.apply(obj,arguments);
    }
  }

  var obj = {
    fn:fn
  }

  var fun = bind(fn,obj);
  fun(); //obj

这样调用,就可以将灵活多变的 this ,牢牢的控制住了,因为 fn 的调用方式为 apply 调用。所以,this 就被绑定在传入的 obj 对象上,在 ES5 当中,函数的原型方法上多了一个 bind。效果与上面的函数基本一致,具体用法限于篇幅就不多说了。

2.4 new 绑定

new 是一个被很多人误解的一个关键字,但实际上 javascript 的 new 与传统面向对象的语言完全不同。
个人把 new 理解为一种特殊的函数调用,当使用 new 关键字来调用函数的时候,会执行下面操作,

  • 创建一个全新的对象
  • 将空对象的 __proto__ 指向构造函数的 prototype
  • 将新对象的 this 绑定到调用的函数上
  • 如果函数返回值为基本类型或者为 this又或者不返回任何值,那么将会返回这个创建的新对象,如果返回了一个对象,那么则会返回这个对象,而不会返回创建的新对象。
  function fn(a){
    this.a = a;
  }
  fn.prototype.hi = function(){
    console.log('hi')
  }

  var obj = new fn(2);

  console.log(obj);

  function fn(a){
    this.a = a;
    return {};
  }

  var obj = new fn(2);

  console.log(obj); //{}

2.5 特殊的传参

null 和 undefined 也是可以作为 this 的绑定对象的,但是实际上应用的是默认的绑定。
但是这种传参的实际效用是什么呢?
常见的用法是将一个数组展开,作为参数传入参数。比如

  function fn(a,b){
    console.log('a:',a,'b:',b);
  }

  fn.apply(null,[1,2]); // a: 1 b: 2

但是这种用法会有一个坑,那就是如果函数存在了 this ,那么就会应用默认的绑定规则,将 this 绑定在全局对象上,发生于预期不一致的情况。为了代码更加稳健,可以使创建一个比空对象更空的对象。

var obj = Object.create(null);
console.log(obj.__proto__); //undefined

var obj2 = {}
console.log(obj2.__proto__); //Object {}

Object原型上有一个 create 方法,这个方法会创建一个对象,然后将对象的原型指向传入的参数,所以传入 null 的话,产生一个没有 prototype 的对象,所以会比空对象更加"空"。

所以传入这个对象,会比传入 null 更加安全。

var obj = Object.create(null);

fn.apply(obj,[1,2]);

2.6 根据作用域来决定 this 的绑定

在 ES6 当中,出现了一个新的函数类型,箭头函数。

如果使用箭头函数,那么就不会使用上面提到的四种 this 绑定方式,而是根据作用域来决定

比较常见的是用于事件函数和定时器的情况。

下面是比较常见的传统 this 写法

  function fn(){
    var _this = this;
    setTimeout(function(){
      console.log(_this.a);
    },100)
  }

  var obj = {
    a:2
  }

  fn.call(obj); //2

如果使用箭头函数则可以这么写

  function fn(){
    setTimeout(()=>{
      //this 来源于 fn 函数的作用域
      console.log(this.a);
    },100)
  }

  var obj = {
    a:2
  }

  fn.call(obj); //2

2.7 事件函数当中 this 的绑定机制

如果是在事件函数当中,this 的绑定是指向触发事件的 DOM 元素的,

$('body')[0].addEventListener('click',function(){
  console.log(this);
},false);

点击 body 元素之后,控制台则会显示 body 元素

3. 小结

如果想判断一个函数的 this 绑定在哪里,首先是找到函数的调用位置,之后是按照规则来判断。

  • 如果函数调用时没有任何修饰条件,那么在严格模式下则会绑定到 undefined ,非严格模式下会绑定到全局。
  • 如果是用对象做上下文,来对函数进行调用,那么则会绑定到调用的这个对象上。
  • 如果是用 call 或者 apply 方法来进行调用的,则会绑定到第一个传入参数上。
  • 如果是使用 new 关键字来调用函数的,则会绑定到新创建的那个对象上.
  • 如果是在事件函数内,则会绑定到触发事件的那个DOM元素上。

以上就是关于Javascript中神奇的this的相关介绍,希望对大家的学习有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript
this
javascript this、javascript中的this、javascript技巧、javascript this对象、javascript中this,以便于您获取更多的相关知识。

时间: 2024-09-10 14:27:04

Javascript中神奇的this_javascript技巧的相关文章

JavaScript中神奇的call()方法

 这篇文章主要介绍了JavaScript中神奇的call()方法,本文用浅显的语言帮助你加深理解call()方法,需要的朋友可以参考下     先看看关于call()的官方解释,"调用一个对象的一个方法,以另一个对象替换当前对象.",看了这样的解释,或许让你更摸不着头脑了.看例子: 代码如下: var x = "我是全局变量"; //定义全局变量x function a(){ //定义函数类结构a this.x = "我是在函数类结构a中声明的哦"

Javascript中replace()小结_javascript技巧

关于定义   replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串. 关于语法 stringObject.replace(regexp/substr,replacement) 关于参数 参数 描述 regexp/substr 必需.规定子字符串或要替换的模式的 RegExp 对象. 请注意,如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象. replacement 必需.一个字符串值.规定了替换文本或生成替

Immutable 在 JavaScript 中的应用_javascript技巧

Mutable 对象 在 JavaScript 中,对象是引用类型的数据,其优点在于频繁的修改对象时都是在原对象的基础上修改,并不需要重新创建,这样可以有效的利用内存,不会造成内存空间的浪费,对象的这种特性可以称之为 Mutable,中文的字面意思是「可变」. 对于 Mutable 的对象,其灵活多变的优点有时可能会成为其缺点,越是灵活多变的数据越是不好控制,对于一个复杂结构的对象来说,一不小心就在某个不经意间修改了数据,假如该对象又在多个作用域中用到,此时很难预见到数据是否改变以及何时改变的.

浅谈javascript中replace()方法_javascript技巧

定义和用法 replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串.语法stringObject.replace(regexp/substr,replacement) 返回值 一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的.说明 字符串 stringObject 的 replace() 方法执行的是查找并替换的操作.它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 r

深入认识JavaScript中的函数_javascript技巧

概述 函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解.JavaScript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的.通过函数对象的性质,可以很方便的将一个函数赋值给一个变量或者将函数作为参数传递.在继续讲述之前,先看一下函数的使用语法: function func1(-){-} var func2=function(-){-}; var func3=function func4(-){-}; var func5=new Functio

关于javascript中的parseInt使用技巧_基础知识

要对表单中填写的日期格式进行客户端验证,于是在网上找了段代码,其中用到parseInt对年月日做判断,其中有类似这样的语句: ...... else if(parseInt(month)<1 || parseInt(month) >12) ...... 可是对于当前本来正确的日期,这里怎么也通不过.后来一查才明白,parseInt实际上有两个参数,第一个是要转换的值,第二个是指定的进制.如果不指定第二个参数,那么它只能正确地转换01到07(即把它们转换成1到7),从08开始,它就会按照&quo

深入理解javascript中concat方法_javascript技巧

最近在恶补js知识的时候,总是会因为js强大的语法而感到震撼.因为以前对前端方面的疏忽,导致了一些理解的错误.因此痛改前非,下定决心,不管做什么事情,都要有专研的精神. 在介绍前,抛出一个问题:如何将多个数组合并为一个数组? 以下的分享会分为如下小节: 1.concat方法的基础介绍 2.从实例中感受concat方法 1.concat方法的基础介绍 concat方法用于多个数组的合并.它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变. console.log([].conca

详解Javascript中的原型OOP_javascript技巧

前言 JavaScript原型链对于对于很多刚学习Javascript的新手们来书总显得神秘不好理解,但如果你想深入的学习Javascript,就不得不去研究以下了,或许你很少有机会来使用它,不过我想说机会总是留给有准备的人,下面我们这篇文章就来学习一下. 百度百科中,这样描述了property:在JavaScript中,prototype对象是实现面向对象的一个重要机制.每个函数就是一个对象(Function),函数对象都有一个子对象 prototype对象,类是以函数的形式来定义的.prot

理解JavaScript中的事件_javascript技巧

在很多语言的学习中,"事件"都是一个比较难理解,但是又是一个很重要的概念.javascript中的事件处理也是一样,正因为有了事件处理,才会出现Ajax拖动的效果.本文就讨论一下JavaScript中的事件处理,读过之后,您就会知道,很多Ajax框架实现拖动效果的原理了. 一. IE Event对象 (一)IE Event对象的主要属性和方法 在IE中有一个专门负责事件处理的对象Event,这个对象负责对事件的处理,含有很多的属性和方法,通过这些方法和属性的调用,就能完成很多的事件处理