1、this的五种情况
this执行主体,谁把它执行的「和在哪创建&在哪执行都没有必然的关系」
- 函数执行,看方法前面是否有“点”,没有“点”,this是window「严格模式下是undefined」,有“点”,“点”前面是谁this就是谁
- 给当前元素的某个事件行为绑定方法,当事件行为触发,方法中的this是当前元素本身「排除attachEvent」
- 构造函数体中的this是当前类的实例
- 箭头函数中没有执行主体,所用到的this都是其所处上下文中的this
- 可以基于Function.prototype上的call/apply/bind去改变this指向
2、call、apply、bind
call
和apply
的唯一区别是参数,第一个参数都是要绑定的上下文
call
可以有多个参数,剩余参数被认为是传递给方法的参数
apply
第二个参数为一个数据,是传递给方法参数的集合
func.call(obj, 10, 20);
func.apply(obj, [10, 20]);
call
原理:利用**'点’定THIS**机制,context.xxx=self “obj.xxx=func” => obj.xxx()
注意:原始值比如n=10;n.xxx=100
不报错,但是n.xxx
拿不到,所以判断如果传入的this上下文不是对象,转为对象
- 把func中的this改为obj
- 并且把params接收的值当做实参传递给func函数
- 并且让func函数立即执行
Function.prototype.call = function call(context, ...params) {// this/self->func context->obj params->[10,20]let self = this,key = Symbol('KEY'), //加的属性不能影响原始obj中的属性result;context == null ? context = window : null;!/^(object|function)$/i.test(typeof context) ? context = Object(context) : null; //原始值加属性不报错,但是拿不到值 判断是否为对象 不是则改为对象context[key] = self;result = context[key](...params);delete context[key];return result;
};
bind
- 和call/apply的区别:并没有把func立即执行
- 把传递进来的obj/10/20等信息存储起来「闭包」
- 执行bind返回一个新的函数 例如:proxy,把proxy绑定给元素的事件,当事件触发执行的是返回的proxy,在proxy内部,再去把func执行,把this和值都改变为之前存储的那些内容
Function.prototype.bind = function bind(context, ...params) {// this/self->func context->obj params->[10,20]let self = this;return function proxy(...args) {// 把func执行并且改变this即可 args->是执行proxy的时候可能传递的值self.apply(context, params.concat(args));};
};
普通函数的调用 + 柯里化的实现
如果是正常的普通函数的调用方法来调用bind返回的函数,那么直接返回像调用call函数那样返回,第一个参数值就是传入bind的asThis值。注意要将前后传入的参数(两次…arg收集到的)同时收集到call函数的参数中,从而实现柯里化。
通过new操作符调用 + 柯里化的实现
如果通过new来调用bind生成的函数(绑定函数)情况又有所不同了
Function.prototype.bind2 = function(asThis,...args) {//这里的this就是调用bind2的函数(普通函数this指向调用者)if (typeof this !== "function") {throw new TypeError("Not a Function");}// 这里保存调用bind的函数以及传给bind后面的参数。var origin = this;let args0 = args;function bound(...args) {if(this instanceof bound) //如果是new的形式来使用绑定函数的return new origin( ...args0,...args)else //如果是普通函数的形式来使用绑定函数的(基本上与call、apply无区别,实现柯里化即可)return origin.call(asThis, ...args0,...args);}bound.prototype = origin.prototype;return bound;}
3、鸭子方法
类数组像数组「结构、一些操作…」,我们让其用数组上的方法「不能直接用」
/* Array.prototype.slice = function slice() { 不传参数是浅拷贝,克隆arr返回一个新数组let result = [];for (let i = 0; i < this.length; i++) {result.push(this[i]);}return result;
}; */
// 克隆arr返回一个新数组
// arr.slice();// 掌握this的好玩应用:鸭子类型
// 像鸭子,我们就说他是鸭子 -> 类数组像数组「结构、一些操作...」,我们让其用数组上的方法「不能直接用」
function func() {console.log(arguments); //js把传入到这个函数的全部参数存储在一个叫做arguments的东西里面// 把arguments变为数组,这样就可以用数组的办法了:Array.from/[...arguments]/...let result = [];for (let i = 0; i < arguments.length; i++) {result.push(arguments[i]);} // Array.prototype.slice -> [].slice // let result = Array.prototype.slice.call(arguments);// console.log(result);[].forEach.call(arguments, item => {console.log(item);});
}
func(10, 20, 30);
4、绑定规则
new绑定》显示绑定》隐式绑定》默认绑定
默认绑定:独立函数调用,可以把默认绑定看作是无法应用其他规则时的默认规则,this指向全局对象。
var name = 'Jenny';
function person() {return this.name;
}
console.log(person()); //Jenny
隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数中的this绑定到这个上下文对象。对象属性引用链中只有上一层或者说最后一层在调用中起作用。
函数还可以作为某个对象的方法调用,这时this就指这个上级对象
function test() {console.log(this.x);
}var obj = {};
obj.x = 1;
obj.m = test;obj.m(); // 1
这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象