ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript のすばらしい this_javascript トリック

JavaScript のすばらしい this_javascript トリック

WBOY
WBOYオリジナル
2016-05-16 15:18:591185ブラウズ

JavaScript のこれは他の言語とはまったく異なるメカニズムであるため、他の言語を作成する一部のエンジニアは混乱する可能性があります。

1. これが関数自体を指していると誤解します

この英語の文法によれば、関数の中に現れるこれを関数そのものとして理解するのは容易です。 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

どちらのメソッドも正しい結果を出力しますが、これがどこにバインドされるかという問題を回避します。物事の動作原理が明確でない場合、多くの場合、頭痛や痛みを引き起こし、醜いコードや保守性の低下につながります。

2. この魔法の拘束ルール

2.1 デフォルトのバインディング ルール

最初のバインディングは最も一般的なバインディングです。以下のコードを見てください。

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

関数 fn は、他の変更を加えずにグローバル スコープで直接呼び出されます。この場合、関数が呼び出されるときに、グローバル オブジェクトを指す this のデフォルト バインディングが使用されます。

これにより、最初の例の this が fn 関数のグローバル変数を指していることが明らかになるため、this.count++ は (ブラウザ環境では) window.count++ と同等になります。もちろん、これは のカウントには影響しません。 fn 関数が影響します。

注意すべき点の 1 つは、上記の状況は非厳密モード (厳密モード) でのみ発生する可能性があることです。厳密モードでは、これはデフォルトで未定義にバインドされます。グローバル変数の汚染を避けるため。

2.2 暗黙的なバインドルール

オブジェクトをコンテキストとして関数が呼び出された場合、このバインディングは変更されます。これは、この関数を呼び出すオブジェクトにバインドされます。次のコードを参照してください:

  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 多層オブジェクトがこのポインタを呼び出す

  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

マルチレベルのオブジェクト参照では、これは呼び出される関数のオブジェクトを指します。

2.2.2 暗黙的な割り当てが失われる可能性があります

以下のコードを表示します

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

fn は obj.fun を参照していますが、関数の呼び出し方法は変更されていないため、これは window にバインドされたままです。
誰もが見落としやすいもう 1 つの状況があります。それは、パラメーターを渡すときに、暗黙的な代入が実際に実行されることです。

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

ビジネスで this のバインディングの要件がある場合、this の暗黙的バインディングは失われる可能性が非常に高いため、あまり推奨される方法ではありません。

2.3 明示的なバインディング ルール

明示的なバインディングは、関数プロトタイプの apply メソッドと call メソッドを使用してこれをバインドします。使用方法は、バインドするオブジェクトを最初のパラメータとして渡すことです。

  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的相关介绍,希望对大家的学习有所帮助。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。