Maison  >  Article  >  interface Web  >  Comment implémenter la liaison en Javascript

Comment implémenter la liaison en Javascript

亚连
亚连original
2018-06-13 15:30:361462parcourir

Cet article présente principalement le processus depuis l'apprentissage de bind jusqu'à l'implémentation de bind en Javascript. Les amis intéressés peuvent suivre et apprendre.

Qu'est-ce que bind ?

La méthode bind() crée une nouvelle fonction et, lorsqu'elle est appelée, définit son mot-clé this pour fournir la valeur. of , lors de l'appel d'une nouvelle fonction, fournit une séquence donnée d'arguments avant toute fourniture.

var result = fun.bind(thisArg[, arg1[, arg2[, ...]]]) 
result(newArg1, newArg2...)

Si vous ne comprenez pas, ne vous inquiétez pas, continuez à lire.

Que fait exactement bind

Trois points ressortent de l'introduction ci-dessus ? Tout d'abord, l'appel de la méthode bind renverra une nouvelle fonction (le corps de cette nouvelle fonction doit être le même que fun). En même temps, deux paramètres sont transmis dans bind. Le premier est pointé par ceci, c'est-à-dire que tout ce qui est transmis est égal à ce qu'il est. Comme indiqué dans le code suivant :

this.value = 2
var foo = {
  value: 1
}
var bar = function() {
 console.log(this.value)
}
var result = bar.bind(foo)
bar() // 2
result() // 1,即this === foo

Le deuxième paramètre est une séquence et vous pouvez y transmettre n'importe quel nombre de paramètres. Et il sera préréglé avant les nouveaux paramètres de fonction.

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家里蹲大学'
}
var result = bar.bind(foo, 'An') //预置了部分参数'An'
result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

Nous pouvons voir que lorsque result(22, 'Study at home in college') est finalement appelé, il contient déjà le 'An' transmis lors de l'appel de bind.

Résumé en une phrase : appeler bind renverra une nouvelle fonction. Ceci dans cette fonction pointe vers le premier paramètre de liaison, et les paramètres suivants seront transmis à l'avance à cette nouvelle fonction. Lors de l'appel de la nouvelle fonction, les paramètres transmis seront placés après les paramètres prédéfinis et transmis ensemble dans la nouvelle fonction.

Implémentez une liaison par vous-même

Pour implémenter une liaison, vous devez implémenter les deux fonctions suivantes

Renvoyer un function et liez ceci, passez les paramètres prédéfinis

La fonction renvoyée par bind peut être utilisée comme constructeur. Par conséquent, lorsqu'il est utilisé comme constructeur, cela devrait être invalide, mais les paramètres transmis sont toujours valides

1. Renvoyez une fonction, liez-la et transmettez les paramètres prédéfinis

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
  console.log(name) // 'An'
  console.log(age) // 22
  console.log(school) // '家里蹲大学'
  console.log(this.value) // 1
}
Function.prototype.bind = function(newThis) {
  var aArgs  = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的预置参数序列
  var that = this
  return function() {
    return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
    //绑定this同时将调用时传递的序列和预置序列进行合并
  }
}
var result = bar.bind(foo, 'An')
result(22, '家里蹲大学')

Il y en a un. Les détails sont Array.prototype.slice.call(arguments, 1). Nous savons que la variable arguments peut obtenir les paramètres transmis lorsque la fonction est appelée, mais ce n'est pas un tableau, mais elle a un attribut de longueur. Pourquoi peut-il être transformé en un tableau pur en l'appelant ainsi ? Ensuite, nous devons revenir au code source de la V8 pour analyse. #Le code source de cette version est une première version avec relativement moins de contenu.

function ArraySlice(start, end) {
 var len = ToUint32(this.length); 
 //需要传递this指向对象,那么call(arguments),
 //便可将this绑定到arguments,拿到其length属性。
 var start_i = TO_INTEGER(start);
 var end_i = len;
 if (end !== void 0) end_i = TO_INTEGER(end);
 if (start_i < 0) {
  start_i += len;
  if (start_i < 0) start_i = 0;
 } else {
  if (start_i > len) start_i = len;
 }
 if (end_i < 0) {
  end_i += len;
  if (end_i < 0) end_i = 0;
 } else {
  if (end_i > len) end_i = len;
 }
 var result = [];
 if (end_i < start_i)
  return result;
 if (IS_ARRAY(this))
  SmartSlice(this, start_i, end_i - start_i, len, result);
 else 
  SimpleSlice(this, start_i, end_i - start_i, len, result);
 result.length = end_i - start_i;
 return result;
};

Vous pouvez voir dans le code source qu'après avoir attribué l'attribut length sous les arguments pour découper l'appel, vous pouvez obtenir le tableau final via start_i et end_i, c'est donc un tableau pur sans le passer dans slice Le tableau peut également obtenir une variable de tableau à la fin.

2. La fonction renvoyée par bind peut être utilisée comme constructeur

Lorsqu'elle est utilisée comme constructeur, elle doit pointer vers l'instance produite par new, et il doit également y avoir un attribut prototype. , qui pointe vers le prototype de l'instance.

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school) {
 ...
 console.log(&#39;this.value&#39;, this.value)
}
Function.prototype.bind = function(newThis) {
 var aArgs  = Array.prototype.slice.call(arguments, 1)
 var that = this //that始终指向bar
 var NoFunc = function() {}
 var resultFunc = function() {
  return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
 } 
 NoFunc.prototype = that.prototype //that指向bar
 resultFunc.prototype = new NoFunc()
 return resultFunc
}
var result = bar.bind(foo, &#39;An&#39;)
result.prototype.name = &#39;Lsc&#39; // 有prototype属性
var person = new result(22, &#39;家里蹲大学&#39;)
console.log(&#39;person&#39;, person.name) //&#39;Lsc&#39;

Le code de simulation ci-dessus fait deux choses importantes.

1. Simulez un attribut prototype pour la fonction renvoyée. , car les propriétés et méthodes définies sur le prototype peuvent être interrogées via l'instance du constructeur new

var NoFunc = function() {}
...
NoFunc.prototype = that.prototype //that指向bar
resultFunc.prototype = new NoFunc()
return resultFunc

Comme le montre le code ci-dessus, qui pointe toujours vers bar. Dans le même temps, la fonction renvoyée a hérité de ce.prototype, qui est bar.prototype. Pourquoi ne pas simplement rendre l'attribut prototype resultFunc.prototype de la fonction renvoyée égal à bar(that).prototype ? En effet, toute nouvelle instance peut accéder à la chaîne de prototypes. S'il est affecté directement, le nouvel objet peut modifier directement la chaîne prototype de la fonction barre, qui est une pollution de la chaîne prototype. Nous utilisons donc l'héritage (attribuons la chaîne de prototypes du constructeur à l'instance du constructeur parent) pour désengager la chaîne de prototypes du nouvel objet de bar.

2. Déterminez si this est utilisé pour une liaison ordinaire ou pour le constructeur lorsqu'il est actuellement appelé pour en changer le point.

Comment déterminer où cela pointe actuellement ? Dès le premier point, nous savons déjà que la nouvelle fonction renvoyée par la méthode bind possède déjà une chaîne prototype. Il ne nous reste plus qu'à changer le pointage. de cela pour le simuler. Comment déterminer la posture actuelle appelée. La réponse est instanceof. L'opérateur

instanceof est utilisé pour tester si un objet possède la propriété prototype d'un constructeur dans sa chaîne de prototypes.

// 定义构造函数
function C(){} 
function D(){} 
var o = new C();
// true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof C; 
// false,因为 D.prototype不在o的原型链上
o instanceof D;

Comme le montre ce qui précède, instanceof peut déterminer si un objet est nouveau grâce à cette fonction. S'il est nouveau, alors la chaîne de prototypes de cet objet doit être le prototype de la fonction.

Jetons donc un coup d'œil à la structure de la fonction renvoyée par la clé :

var resultFunc = function() {
  return that.apply(this instanceof that ? 
    this : 
    newThis, 
    aArgs.concat(Array.prototype.slice.call(arguments)))
 }

En cela, nous devons d'abord reconnaître que this dans cette instance de that est ce qui est renvoyé après l'appel de la fonction de liaison this. dans la nouvelle fonction. Cela peut donc être exécuté dans un environnement de portée normale, et il peut également être nouveau de changer de direction. En y repensant, cela pointe toujours vers bar, et sa chaîne de prototypes that.prototype existe toujours. Donc, si cette nouvelle fonction effectue maintenant une nouvelle opération, alors ceci pointe vers la nouvelle fonction, alors cette instance de cela === true, alors transmettez-la dans apply comme pointeur, qui pointe vers la nouvelle fonction. S'il s'agit d'un appel normal, alors celui-ci n'est pas créé par new, c'est-à-dire que la nouvelle fonction n'est pas utilisée comme constructeur, et cette instance de === false est évidente. Cette fois, c'est un appel de liaison normal. Utilisez simplement le premier paramètre de l’appel comme pointeur vers ceci.

完整代码(MDN下的实现)

if (!Function.prototype.bind) {
 Function.prototype.bind = function(oThis) {
  if (typeof this !== &#39;function&#39;) {
   // closest thing possible to the ECMAScript 5
   // internal IsCallable function
   throw new TypeError(&#39;Function.prototype.bind - what is trying to be bound is not callable&#39;);
  }

  var aArgs  = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP  = function() {},
    fBound = function() {
     return fToBind.apply(this instanceof fNOP
         ? this
         : oThis,
         aArgs.concat(Array.prototype.slice.call(arguments)));
    };

  if (this.prototype) {
   // Function.prototype doesn&#39;t have a prototype property
   fNOP.prototype = this.prototype; 
  }
  fBound.prototype = new fNOP();
  return fBound;
 };
}

可以看到,其首先做了当前是否支持bind的判定,不支持再实行兼容。同时判断调用这个方法的对象是否是个函数,如果不是则报错。

同时这个模拟的方法也有一些缺陷,可关注MDN上的Polyfill部分

小结

模拟bind实现最大的一个缺陷是,模拟出来的函数中会一直存在prototype属性,但是原生的bind作为构造函数是没有prototype的,这点打印一下即可知。不过这样子new出来的实例没有原型链,那么它的意义是什么呢。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

MVVM框架如何解析双向绑定

JS运动特效

JS中链式运动(详细教程)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn