关于bind的笔记

152 阅读6分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

bind的特点

• bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。 • 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被略,同时调用时的参数被提供给模拟函数。

 if (!Function.prototype.bind) {
     //防止Function.prototype.bind.call(obj,param)这种调用改变this
   Function.prototype.bind = function(oThis) {
     if (typeof this !== 'function') {
       throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
     }
     var aArgs   = Array.prototype.slice.call(arguments, 1),
         fToBind = this,
         fNOP    = function() {},
         fBound  = function() {
           // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
           return fToBind.apply(this instanceof fBound
                  ? this
                  : oThis,
                  // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
         };
     // 维护原型关系
     if (this.prototype) {
       // 当执行Function.prototype.bind()时, this为Function.prototype 
       // this.prototype(即Function.prototype.prototype)为undefined
       fNOP.prototype = this.prototype; 
     }
     // 下行的代码使fBound.prototype是fNOP的实例,因此
     // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
     fBound.prototype = new fNOP();
     return fBound;
   };}

词法作用域:

JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。 而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

 var value = 1;
 function foo() {
     console.log(value);
 }
 function bar() {
     var value = 2;
     foo();
 }
 bar();

• 假设JavaScript采用静态作用域,让我们分析下执行过程: ○ 执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置, 查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。 • 假设JavaScript采用动态作用域,让我们分析下执行过程: ○ 执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的 作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。 • 前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。

执行顺序:
 var foo = function () {
     console.log('foo1');
 }
 foo();  // foo1
 var foo = function () {
     console.log('foo2');
 }
 foo(); // foo2
然而去看这段代码:
 function foo() {
     console.log('foo1');
 }
 foo();  // foo2
 function foo() {
     console.log('foo2');
 }
 foo(); // foo2

可执行代码(executable code)的类型:全局代码、函数代码、eval代码。 举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。

执行上下文栈:
 function fun3() {
     console.log('fun3')
 }
 function fun2() {
     fun3();
 }
 function fun1() {
     fun2();
 }
 fun1();
 

// 伪代码 // fun1()ECStack.push( functionContext); // fun1中竟然调用了fun2,还要创建fun2的执行上下文ECStack.push( functionContext); // 擦,fun2还调用了fun3!ECStack.push( functionContext); // fun3执行完毕ECStack.pop(); // fun2执行完毕ECStack.pop(); // fun1执行完毕ECStack.pop(); // javascript接着执行下面的代码,但是ECStack底层永远有个globalContext 变量对象:

对于每个执行上下文,都有三个重要属性: • 变量对象(Variable object,VO) • 作用域链(Scope chain) • this 变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。 全局上下文: 全局上下文中的变量对象就是全局对象!

函数上下文:

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

执行过程:

  1. 进入执行上下文

  2. 代码执行 进入执行上下文 当进入执行上下文时,这时候还没有执行代码, 变量对象会包括:

  3. 函数的所有形参 (如果是函数上下文) ○ 由名称和对应值组成的一个变量对象的属性被创建 ○ 没有实参,属性值设为 undefined

  4. 函数声明 ○ 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建

○ 如果变量对象已经存在相同名称的属性,则完全替换这个属性

  1. 变量声明

    ○ 由名称和对应值(undefined)组成一个变量对象的属性被创建; ○ 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

 function foo(a) {
   var b = 2;
   function c() {}
   var d = function() {};
 
   b = 3;
 
 }
 foo(1);
  //在进入执行上下文后,这时候的 AO 是
 AO = {
     arguments: {
         0: 1,
         length: 1
     },
     a: 1,
     b: undefined,
     c: reference to function c(){},
     d: undefined
 }

代码执行 在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值 还是上面的例子,当代码执行完后,这时候的 AO 是:

 AO = {
     arguments: {
         0: 1,
         length: 1
     },
     a: 1,
     b: 3,
     c: reference to function c(){},
     d: reference to FunctionExpression "d"
 }

作用域链

• 查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。 • 函数的作用域在函数定义的时候就决定了!!!!!!。 • 这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

 function foo() {
     function bar() {
         ...
    }
 }
函数创建时,各自的[[scope]]为:
 foo.[[scope]] = [
   globalContext.VO
 ];
 
 bar.[[scope]] = [
     fooContext.AO,
     globalContext.VO
 ];

函数激活 当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。 这时候执行上下文的作用域链,我们命名为 ScopeChain

函数柯里化 在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

 var curry = function (fn) {
     var args = [].slice.call(arguments, 1);
     return function() {
         var newArgs = args.concat([].slice.call(arguments));
         return fn.apply(this, newArgs);
     };
 };
 
 function add(a, b) {
     return a + b;
 }
 var addCurry = curry(add, 1, 2);
 addCurry() // 3//或者var addCurry = curry(add, 1);addCurry(2) // 3//或者var addCurry = curry(add);addCurry(1, 2) // 3