一:从this指向开始
1: this指向分类
this指向分为默认绑定,隐式绑定,显式绑定,new操作,箭头函数这几类。
(1): 默认绑定:
声明一个函数:
function test1 () {
console.log(this); // window
};
test1();
在window的非严格模式下: this指向为window. 在严格模式下为undefined
(2): 隐式绑定:函数引用有上下文的时候, 隐式绑定为这个上下文。
(3): important: 显示绑定:
显示绑定的意思就是可以用户手动的绑定this指向。
经常使用的改变this指向的方法就是call,apply, bind,后面会详细介绍。
(4): new操作, 通过new操作符可以让this指向固化在实例上。
(5): 箭头函数: 箭头函数的this指向它外层普通函数的this指向。
function foo () {
var vm = () => {
console.log(this);
};
vm(); // Window
};
二:Function.prototype.call
call方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。 ---MDN
function.call(thisArg, arg1, arg2, ...)
(1): 常见的使用
通过call方法可以实现继承:
function Father () {
this.books = ['js', 'vue'];
this.name = 'yhq';
};
Father.prototype.sayBooks = function () {
console.log(this.books);
};
function Son () {
Father.call(this);
};
var son = new Son();
son.books // ["js", "vue"]
son.sayBooks // Uncaught TypeError: son.sayBooks is not a function
这种继承只能继承父类上的属性和方法,不能继承父类原型上的。
通过call方法调用匿名函数:
var animals = [
{ species: 'Lion', name: 'King' },
{ species: 'Whale', name: 'Fail' }
];
for (var i = 0; i < animals.length; i++) {
(function(i) {
this.print = function() {
console.log('#' + i + ' ' + this.species
+ ': ' + this.name);
}
this.print();
}).call(animals[i], i);
}
(2): 实现一个call
fn.call(obj, args1, args2,...);
call方法接受一个参数是obj。让this指向这个obj。我们的想法是把fn已属性的方式加入这个obj中,执行call操作之后delete这个新增的属性。
第一版代码:
Function.prototype.myCallOne = function (ctx) {
var args = [...arguments].slice(1); // 这里使用es6的方法,取出了this外的所有参数
ctx.fn = this; // 改变this指向
var result = ctx.fn(...args); // 执行函数,因为args为数组,所以解构出来
delete ctx.fn;
return result;
}
我们来测试一下:
var obj = {
name: 'yhq'
};
function funcs(age) {
console.log('name:'+ this.name + 'age:' + age);
};
funcs.myCallOne(obj, 24); // name:yhq age:24
ok ! 没有问题。
优化一下:
这里有几个小问题: 如果没有call的时候没有传入obj呢? 如果原有的对象上有fn呢?
处理这两个问题: 如果没有传入默认为window, 保证fn的唯一性。
第二个版本:
Function.prototype.myCallTwo = function (ctx) {
ctx = ctx || window;
var fn = Symbol(); // Symbol属性来确定fn唯一。
var args = [...arguments].slice(1);
ctx[fn] = this;
var result = ctx[fn](...args);
delete ctx[fn];
return result;
}
测试一下:
var obj2 = {
name: 'yhq',
fn: function () {}
};
function practive(age) {
console.log('name:'+ this.name + 'age:' + age);
};
funcs.myCallTwo(obj, 24); // name:yhq age:24
ok啦。。
三: Function.prototype.apply
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数 ---MDN
apply()和call()的使用场景基本相同,主要就是接受的参数不一样1,apply接受的是一个数组,call接受的是一个参数列表。
实现一个apply:
fn.apply(this, []);
思路类似于call,注意一点,可以通过直接传入一个array来接受参数,不需要通过arguments
代码如下:
Function.prototype.myBind = function (ctx, array) {
ctx = ctx || window;
var fn = Symbol();
var result;
if (!array) { // 判断array是否存在
result = ctx[fn]();
}
else {
result = ctx[fn](...array);
}
delete ctx[fn];
return result;
};
测试一下:
var obj2 = {
name: 'yhq',
fn: function () {}
};
function practive(age) {
console.log('name:'+ this.name + 'age:' + age);
};
funcs.myBind(obj, [24]); // name:yhq age:24
OK!
四: Function.prototype.bind
**bind()**
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用 ---MDN
fn.bind(obj, args1, args2...); 返回的是一个执行上下文函数,需要用户手动调用才能执行。
bind函数有如下特性:
1. 改变this指向
2. 接受参数列表
3. return 一个函数
4. 遇到new这样优先级高于bind方法的时候,bind改变this指向会无效。
ok! 我们来实现一下。
Function.prototyep.myBind = function (ctx) {
var self = this; // 因为之后要return一个函数,这里保存一下this防止混乱
var args = Array.prototype.slice.call(arguments, 1); // 获取参数
var fb = function () {
var bindArgs = Array.prototype.slice.call(arguments, 1);
return self.apply( this instanceof fb ? this : ctx,
args.concat(bindArgs)) };
var fn = function () {};
fn.prototype = this.prototype; // 让实例可以继承原型
fb.prototype = new fn();
return fb;
}
解析几个问题:
self.apply( fb instance this ? this : ctx, args.concat(bindArgs));
如果是构造函数,this指向的是实例, this instanceof fb === true, 此时不需要修改this,直接取原来的this。 或者为ctx。
new操作的话,需要实例可以继承原型。所以这里有个继承的关系。
通过一个fn的中介来继承原型。
基本实现就是这样子,之后我在慢慢完善。