bind
공식 설명
bind() 함수는 새로운 함수(바운드 함수라고 함)를 생성하며, 새 함수는 호출된 함수(대상 함수)와 동일한 관계를 갖습니다. 바인딩된 함수의) 동일한 함수 본문(ECMAScript 5 사양의 내장 호출 속성). 대상 함수가 호출되면 this 값은 바인딩()의 첫 번째 매개변수에 바인딩되며 재정의될 수 없습니다. 바인딩된 함수가 호출되면 바인딩()은 원래 함수에 제공하기 위해 미리 설정된 매개변수도 허용합니다. 바인딩된 함수는 new 연산자를 사용하여 객체를 생성할 수도 있습니다. 이는 생성자로서 원래 함수처럼 동작합니다. 제공된 이 값은 무시되고 호출에 대한 인수는 모의 함수에 제공됩니다.
사용 소개
JavaScript의 범위는 실행되는 환경에 따라 결정되므로 함수 정의가 실제로 실행되는 환경과 다른 경우가 많기 때문에 범위는 해당 변경 사항도 발생합니다.
예를 들어 다음과 같은 상황입니다.
var id = 'window'; //定义一个函数,但是不立即执行 var test = function(){ console.log(this.id) } test() // window //把test作为参数传递 var obj = { id:'obj', hehe:test } //此时test函数运行环境发生了改变 obj.hehe() // 'obj' //为了避免这种情况,javascript里面有一个bind方法可以在函数运行之前就绑定其作用域,修改如下 var id = 'window'; var test = function(){ console.log(this.id) }.bind(window) var obj = { id:'obj', hehe:test } test() // window obj.hehe() // window
위에서 소개한 것처럼 함수의 범위를 바인딩하는 것이 바인드 메소드의 중요한 역할이지만 바인드 메소드는 호환되지 않습니다. 낮은 버전의 브라우저는 여기서 수동으로 할 수 있습니다.
핵심 아이디어 분할
bind 메소드는 함수를 바로 실행하지 않기 때문에 실행할 함수를 반환해야 한다(여기서는 클로저를 사용하고, 함수를 반환할 수 있음) return function(){ }
범위 바인딩, 여기서는 apply 또는 call 메서드를 사용하여 xx.call(yy)/xx.apply(yy)
매개변수 전달을 구현할 수 있습니다. 매개변수의 불확실성, 배열을 전달하려면 적용을 사용해야 합니다(예제는 더 명확합니다). xx.apply(yy,[...Array...]). 호출을 사용하면 편리하지 않습니다. 호출은 하나씩 나열되어야 합니다
구현
위의 아이디어로 대략적인 프로토타입은 이미 명확하고 코드는 구현하기 쉬워야 합니다
바인드 범위 , 바인드 매개변수 전송
Function.prototype.testBind = function(that){ var _this = this, /* *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性 *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外, *后面的所有参数都需要作为数组参数传递 *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1) */ slice = Array.prototype.slice, args = slice.apply(arguments,[1]); //返回函数 return function(){ //apply绑定作用域,进行参数传递 return _this.apply(that,args) } }
테스트
var test = function(a,b){ console.log('作用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 作用域绑定 ok // testBind参数传递 also ok // 调用参数传递 undefined
동적 매개변수
바인드 메소드의 범위 바인딩은 위에서 구현했지만 연고에 파리는 함수를 반환하므로 매개변수 전달을 호출할 때 지원되어야 합니다. 분명히 위의 fun_new는 호출할 때 매개변수 전달을 지원하지 않습니다. 왜냐하면 우리가 호출하게 되는 것은 이 반환 함수이기 때문입니다.
function(){ return _this.apply(that,args) } 这里面的args在绑定的时候就已经确定了,调用的时候值已经固定, 我们并没有处理这个function传递的参数。
변환합니다
return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
Array.prototype.slice.apply(arguments,[0]) 여기서 반환 함수가 실행될 때 전달되는 일련의 매개 변수를 의미하며, 따라서 첫 번째 매개변수 [0]부터 시작하고 이전 args = Slice.apply(arguments,[1])은 testBind 메서드가 실행될 때 전달된 매개변수를 참조하므로 두 번째 매개변수 [1]부터 시작하여 두 개는 다음과 같습니다. 본질적으로 다르며 혼동할 수 없습니다. 둘이 병합된 후에만 반환되는 함수의 전체 매개 변수는
이므로 다음과 같은 구현이 있습니다.
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]); return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } }
Test
var test = function(a,b){ console.log('作用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 作用域绑定 ok // testBind参数传递 also ok // 调用参数传递 hello bind
위의 두 가지 매개변수 전달 방법에서 바인드의 우선순위가 높다는 것은 args.concat(Array.prototype.slice.apply(arguments,[0]))에서도 확인할 수 있습니다. 배열 앞에.
프로토타입 체인
공식 문서에는 다음과 같은 문장이 있습니다.
바운드 함수는 new 연산자를 사용하여 구성할 수도 있습니다. 마치 대상 함수가 대신 생성된 것처럼 보입니다.
제공된 이 값은 무시되는 반면 앞에 추가된 인수는
에뮬레이트된 함수에 제공됩니다.
바인딩된 함수를 나타냅니다. is new가 인스턴스화되면 원래 함수의 프로토타입 체인 메서드를 상속해야 하며 바인딩 프로세스 중에 제공된 this는 무시되지만(원래 함수의 this 개체는 상속됨) 매개 변수는 계속 사용됩니다.
여기서 프로토타입 체인을 전달하기 위한 전달 함수가 필요합니다
그리고 바인드 메소드의 첫 번째 매개변수인 this는 전달할 필요가 없으며 나누어야 합니다.fNOP = function () {} //创建一个中转函数 fNOP.prototype = this.prototype; xx.prototype = new fNOP() 修改如下 Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //所以调用官方bind方法之后 有一个name属性值为 'bound ' bound = function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
바인드 후 메소드를 직접 호출
JavaScript는 기본적으로 바인드 분석을 단계별로 구현합니다var f = function () { console.log('不传默认为'+this) };f.bind()() // 不传默认为 Window
bind() 함수는 새로운 함수( 바인딩 함수), 새 함수는 동일합니다. 피호출자 함수(바인딩된 함수의 대상 함수)는 동일한 함수 본문(ECMAScript 5 사양의 내장 호출 속성)을 갖습니다. 대상 함수가 호출되면 this 값은 바인딩()의 첫 번째 매개변수에 바인딩되며 재정의될 수 없습니다. 바인딩된 함수가 호출되면 바인딩()은 원래 함수에 제공하기 위해 미리 설정된 매개변수도 허용합니다.
저자 : So and so 출처 : Segmentfault|2016-11-02 18:54
소장품
공유
bind
공식 설명
bind() 함수는 새 함수(바인딩 함수라고 함), 새 함수 및 호출된 함수( 바인딩 함수의 대상 함수)는 동일한 함수 본문(ECMAScript 5 사양의 내장 호출 속성)을 갖습니다. 대상 함수가 호출되면 this 값은 바인딩()의 첫 번째 매개변수에 바인딩되며 재정의될 수 없습니다. 바인딩된 함수가 호출되면 바인딩()은 원래 함수에 제공하기 위해 미리 설정된 매개변수도 허용합니다. 바인딩된 함수는 new 연산자를 사용하여 객체를 생성할 수도 있습니다. 이는 생성자로서 원래 함수처럼 동작합니다. 제공된 이 값은 무시되고 호출에 대한 인수는 모의 함수에 제공됩니다.
사용 소개
JavaScript의 범위는 실행되는 환경에 따라 결정되므로 함수 정의가 실제로 실행되는 환경과 다른 경우가 많기 때문에 범위는 해당 변경 사항도 발생합니다.
예를 들어 다음과 같은 상황입니다.
var id = 'window'; //定义一个函数,但是不立即执行 var test = function(){ console.log(this.id) } test() // window //把test作为参数传递 var obj = { id:'obj', hehe:test } //此时test函数运行环境发生了改变 obj.hehe() // 'obj' //为了避免这种情况,javascript里面有一个bind方法可以在函数运行之前就绑定其作用域,修改如下 var id = 'window'; var test = function(){ console.log(this.id) }.bind(window) var obj = { id:'obj', hehe:test } test() // window obj.hehe() // window
上面介绍了bind方法的一个重要作用就是为一个函数绑定作用域,但是bind方法在低版本浏览器不兼容,这里我们可以手动实现一下。
拆分一下关键思路
因为bind方法不会立即执行函数,需要返回一个待执行的函数(这里用到闭包,可以返回一个函数)return function(){}
作用域绑定,这里可以使用apply或者call方法来实现 xx.call(yy)/xx.apply(yy)
参数传递,由于参数的不确定性,需要用apply传递数组(实例更明了)xx.apply(yy,[...Array...]),如果用call就不太方便了,因为call后面的参数需要一个个列出来
实现
有了上述的思路,大致的雏形已经明了了,代码应该也很容易实现
绑定作用域,绑定传参
Function. prototype. testBind = function(that){ var _this = this, /* *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性 *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外, *后面的所有参数都需要作为数组参数传递 *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1) */ slice = Array.prototype.slice, args = slice.apply(arguments,[1]); //返回函数 return function(){ //apply绑定作用域,进行参数传递 return _this.apply(that,args) } }
测试
var test = function(a,b){ console.log('作用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 作用域绑定 ok // testBind参数传递 also ok // 调用参数传递 undefined
动态参数
上面已经实现了bind方法的作用域绑定,但是美中不足的是,既然我们返回的是一个函数,调用的时候应该支持传递参数,很显然,上面的 fun_new 调用的时候并不支持传参,只能在 testBind 绑定的时候传递参数,因为我们最终调用的是这个返回函数
function(){ return _this.apply(that,args) } 这里面的args在绑定的时候就已经确定了,调用的时候值已经固定, 我们并没有处理这个function传递的参数。
我们对其进行改造
return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
这里的 Array.prototype.slice.apply(arguments,[0]) 指的是这个返回函数执行的时候传递的一系列参数,所以是从第一个参数开始 [0] ,之前的args = slice.apply(arguments,[1])指的是 testBind方法执行时候传递的参数,所以从第二个开始 [1],两则有本质区别,不能搞混,只有两者合并了之后才是返回函数的完整参数
所以有如下实现
Function. prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]); return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } }
测试
var test = function(a,b){ console.log('作用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 作用域绑定 ok // testBind参数传递 also ok // 调用参数传递 hello bind
在以上2种传参方式中,bind的优先级高,从 args.concat(Array.prototype.slice.apply(arguments,[0])) 也可以看出来,bind的参数在数组前面。
原型链
官方文档上有一句话:
A bound function may also be constructed using the new operator: doing
so acts as though the target function had instead been constructed.
The provided this value is ignored, while prepended arguments are
provided to the emulated function.
说明绑定过后的函数被new实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的this被忽略(继承原函数的this对象),但是参数还是会使用。
这里就需要一个中转函数把原型链传递下去
fNOP = function () {} //创建一个中转函数 fNOP.prototype = this.prototype; xx.prototype = new fNOP() 修改如下 Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //所以调用官方bind方法之后 有一个name属性值为 'bound ' bound = function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
而且bind方法的第一个参数this是可以不传的,需要分2种情况
直接调用bind之后的方法
var f = function () { console.log('不传默认为'+this) };f.bind()() // 不传默认为 Window
所以直接调用绑定方法时候 apply(that, 建议改为 apply(that||window,,其实不改也可以,因为不传默认指向window
使用new实例化被绑定的方法
容易糊涂,重点在于弄清楚标准的bind方法在new的时候做的事情,然后就可以清晰的实现
这里我们需要看看 new 这个方法做了哪些操作 比如说 var a = new b()
创建一个空对象 a = {},并且this变量引用指向到这个空对象a
继承被实例化函数的原型 :a.__proto__ = b.prototype
被实例化方法b的this对象的属性和方法将被加入到这个新的 this 引用的对象中: b的属性和方法被加入的 a里面
新创建的对象由 this 所引用 :b.call(a)
通过以上可以得知,如果是var after_new = new bindFun(); 由于这种行为是把原函数当成构造器,那么那么最终实例化之后的对象this需要继承自原函数, 而这里的 bindFun 目前是
function(){ return _this.apply(that || window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
这里apply的作用域是绑定的that || window,在执行 testBind()的时候就已经固定,并没有把原函数的this对象继承过来,不符合我们的要求,我们需要根据apply的特性解决这个问题:
在一个子构造函数中,你可以通过调用父构造函数的 `apply/call` 方法来实现继承
例如
function Product(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } //等同于(其实就是把Product放在Food内部执行了一次) function Food(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } this.category = 'food'; }
所以在new新的实例的时候实时将这个新的this对象 进行 apply 继承原函数的 this 对象,就可以达到 new 方法里面的第 3 步的结果
apply(that||window, //修改为 如果是new的情况,需要绑定new之后的作用域,this指向新的实例对象 apply(isNew ? this : that||window, ==> Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //所以调用官方bind方法之后 有一个name属性值为 'bound ' bound = function(){ return _this.apply(isNew ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
这里的 isNew 是区分 bindFun 是直接调用还是被 new 之后再调用,通过原型链的继承关系可以知道,
bindFun 属于 after_new的父类,所以 after_new instanceof bindFun 为 true,同时
bindFun.prototype = new fNOP() 原型继承; 所以 fNOP 也是 after_new的父类, after_new instanceof fNOP 为 true
最终结果
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, bound = function(){ //这里的this指的是调用时候的环境 return _this.apply(this instanceof fNOP ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
我看到有些地方写的是
this instanceof fNOP && that ? this : that || window,
我个人觉得这里有点不正确,如果绑定时候不传参数,那么that就为空,那无论怎样就只能绑定 window作用域了。
以上就是javascript原生一步步实现bind分析的内容,更多相关内容请关注PHP中文网(www.php.cn)!