call
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
function sub() {
this.color = ['red','green']
}
function sup() {
// 此处this指向sup新实例,在新实例中调用sub()
sub.call(this)
}
let sup1 = new sup()
sup1.color.push('black')
console.log(sup1.color) //["red", "green", "black"]
let sup2 = new sup()
console.log(sup2.color) //["red", "green"]
比如这种盗用构造函数的继承方式,在构造函数中内部调用sub.call(this),this指向由sup创建的新实例,这就相当于将sub挂载到新实例上。
主要两点
- call改变了this的指向
- 执行了绑定的函数
模拟实现
Function.prototype.call2 = function(context) {
// 首先要获取调用call的函数,用this可以获取
context.fn = this;
context.fn();
delete context.fn;
}
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
首先改变this的指向,我们将bar设置为foo的一个方法,就像这样
foo:{
bar:function(){}
}
这样bar中this的始终指向foo,随后在调用下bar,再利用delete删除这个方法,这样就实现了call方法。
改写下盗用构造函数的继承方式:
Function.prototype.call2 = function(context) {
context.fn = this;
context.fn();
delete context.fn;
}
function sub() {
this.color = ['red','green']
}
function sup() {
// 此处this指向sup新实例,在新实例中调用sub()
sub.call2(this)
}
let sup1 = new sup()
sup1.color.push('black')
console.log(sup1.color) //["red", "green", "black"]
let sup2 = new sup()
console.log(sup2.color) //["red", "green"]
然而正宗的call还可以接受不定参数的,也就是接受参数的个数不受限制,可以用到arguments属性来获取传入call2的参数列表,改写如下:
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args)
delete context.fn;
}
因为传入的参数列表中第一个是绑定this的目标对象,所以拿到第二个往后的参数,并依次传入。
在用使用call中经常遇到第一个参数传入null的,这是this就会指向window。还有一点,函数可以是有返回值的,接下来改进下
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
var result = context.fn(...args);
delete context.fn
return result;
}
apply
apply与call基本类似,区别在于apply的第二个参数是一个数组,有了call的经验,apply就比较简单了
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push(arr[i]);
}
result = context.fn(...args)
}
delete context.fn
return result;
}
bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数
用法
bind() 最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 this 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,期望方法中的 this 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题:
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
bind() 的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 bind() 的参数写在 this 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。
function list() {
return Array.prototype.slice.call(arguments);
}
function addArguments(arg1, arg2) {
return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
var result2 = addThirtySeven(5);
// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略
绑定函数自动适应于使用 new 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前 :
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x + ',' + this.y;
};
var p = new Point(1, 2);
p.toString(); // '1,2'
var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 本页下方的 polyfill 不支持运行这行代码,
// 但使用原生的 bind 方法运行是没问题的:
var YAxisPoint = Point.bind(null, 0/*x*/);
/*(译注:polyfill 的 bind 方法中,如果把 bind 的第一个参数加上,
即对新绑定的 this 执行 Object(this),包装为对象,
因为 Object(null) 是 {},所以也可以支持)*/
var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'
axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new YAxisPoint(17, 42) instanceof Point; // true
模拟实现
bind函数主要有两个特点:
- 返回一个函数
- 可以传参
Function.prototype.bind2 = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
测试一下
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind2(foo, 'daisy');
bindFoo('18');
//1
//daisy
//18
这里首先保存了this的指向,这里的this就是调用bind2函数的方法,也就是我们要绑定的函数,然后根据arguments来获得传入的参数,在返回函数中,再次获取传入的参数,利用concat合并成新的数组传入要绑定的函数。
当返回的函数当做一个构造函数时,绑定的this会失效,this会指向新的实例,可以使用this instanceof fBound来判断fBound是否当成了构造函数,
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
fBound.prototype = this.prototype;
return fBound;
}