关于this
-
this 是 JavaScript 的一个关键字,一般指向调用它的对象。
1.1. this 指向的应该是一个对象,更具体地说是函数执行的上下文对象
1.2. 如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)
-
this的值是在执行的时候才能确认,定义的时候不能确认,像dom 绑定事件来说,最后执行dom的绑定
this 指向四种情况
总体来说this 绑定形式分为四种,默认绑定、隐式绑定、显示绑定、new绑定
- 默认绑定函数没有被绑定到某个对象上进行调用,这类调用指向window,可以理解成是window调用这些函数
function a() {
console.log(this)
}
a()
- 隐式绑定:是通过某个对象发起的函数调用,这类this 指向往往是发起对象
const a = {
fun(){
console.log(this) // a
}
}
a.fun()
- 显示绑定:使用call和apply方法,间接的将this绑定到了这个对象上,这类操作们明确的绑定了this指向的对象
const a = {}
function b() {
console.log(this) // a
}
b.call(a)
4.new绑定:这里this指向是其返回新函数(注:前提构造函数没有返回其他对象)
- 注:总体来说常见日常使用都是遵循谁调用指向谁的规则
call、apply 的指向
1.call、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数,三者区别bind 虽然改变了 func 的 this 指向,但不马上执行,'call、apply'是在改变了函数的 this 指向之后立马执行
func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
如果是非严格模式'thisArg 为null/undefined' 指向是window,基本类型指向其包装对象,严格模式'null/undefined' 指向是undefined,基本类型为本身
2.使用它们仅仅是改变this这么简单么?现在来想举个例子忽然你只是一下想玩游戏机了,但是你没有,你去 借了一台玩完了 换了回去,首先游戏机你是临时想玩,如果去买一个以后大概率吃灰了,借一下你却体验了 游戏的感觉还没有花钱,这个白嫖的过程带入'call' 'apply' 'bind' 来看,有些方法我对象没有但是我只是忽然间用一下我向你白嫖一下'既达到了目的,又节省重复定义,节约内存空间'
- 实现
1.首先call 和 apply 只有方法才能调用,因此需要让每个方法都要具备这两个功能
2.指向对象 要有借这个动作,并且借完之后就销毁(理解成游戏机还回去了)
实现call
Function.prototype.newCall = function(context,...args){
// this 谁调用指向谁,此时是function
context.fn = this
var context = context || window;
// 我要借你的function 方法,这里fn 不是固定的
// 相当于context 对象 借了一个叫fn 先存起来
context.fn = this;
// 我使用一下 到了我玩一下这个 你也可以直接context.fn(...args) 使用
var result = eval('context.fn(...args)');
// 用完了还给你
delete context.fn
return result;
}
// apply 同理
Function.prototype.newApply = function (context, args) {
let context = context || window;
context.fn = this;
let result = eval('context.fn(...args)');
delete context.fn
return result;
}
实现一个bind
1.call 和 apply 与bind 的区别是前者立即执行,后者是返回函数,但是二者本质都是改变
this指向,这其实利用闭包思考,我返回的一个函数但是函数内部通过call 执行
function bind(context, ...params) {
let self = this;
return function proxy(...args) {
params = params.concat(args);
return self.call(context, ...params);
};
};
- 更更多情况思考案例
Function.prototype.newBind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("this must be a function");
}
var self = this;
// 本质上来思考 bind是和 apply 和 call 很类似,区别是bind返回的是function
// apply 和 call 是直接执行 因此本质上借用bind借用apply但是包裹在function
// 中不让其执行并且返回function
var fbound = function () {
self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}
if(this.prototype) {
fbound.prototype = Object.create(this.prototype);
}
return fbound;
}
案例说明
- 案例 一
- 下面函数fn 是通过对象'o'调用的,因此根据上面'this一般指向'调用它的对象'那么此时的this 就可以 很好的理解为什么打印的是'o'
// 代码 1
var o = {
fn() {
console.log(this)
}
}
o.fn() // o
- 案例二
- 代码2和代码1同理,不同点是 a 是通过类A 创建的,但是方法的调用是a调用的,因此this 是a
// 代码 2
class A {
fn() {
console.log(this)
}
}
var a = new A()
a.fn()// a
- 案例三
1.下面fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象 指向是window
// 代码 3
function fn() {
console.log(this)
}
fn() // 浏览器:Window;Node.js:global
- 案例四
- 虽然现在的fn 是在fn2内部执行,但是fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象 指向是window
function fn() {console.log(this)}
function fn2() {fn()}
fn2() // Window;Node.js:global
- 案例五
- 虽然fn 是在函数fn2 中执行,并且fn2是对象obj 调用,fn2 的this 指向是obj,但是fn2()中的 this 指向并不会传递给函数 fn(),因此fn也是window
function fn() {console.log(this)}
function fn2() {fn()}
var obj = {fn2}
obj.fn2() // Window;Node.js:global
- 案例六
- fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象指向是window,但是这里要注意一点 ES6 下的 class 内部默认采用的是严格模式,严格模式下不在是window 而是 undefined
class B {
fn() {
console.log(this)
}
}
var b = new B()
var fun = b.fn
fun() // undefined
es6特殊箭头函数
1.不绑定 arguments 对象,也就是说在箭头函数内访问 arguments 对象会报错;
2.不能用作构造器,也就是说不能通过关键字 new 来创建实例;
3.默认不会创建 prototype 原型属性;
4.不能用作 Generator() 函数,不能使用 yeild 关键字。
5.'this' 指向是由最外层最近的一层非箭头函数
- 案例一
- 'this' 指向是由最外层最近的一层非箭头函数,当然你也需要看着最外层非箭头函数的函数this指向 是谁,那么对应的箭头函数this指向就是谁
- 下面的getName 箭头函数虽然是a调用的但是要明确,箭头函数是看最近的非箭头函数的指向, 下面的getName 不存在被非箭头函数包裹,因此它的this 就是window
const a = {
name:'w',
getName:()=>{
console.log(this)
}
}
a.getName() // window
- 案例二
- 箭头t 最外层非箭头函数是getName,此时getName 是 a 调用,所以箭头函数就是this 就是a
const a = {
name:'w',
getName(){
const t = ()=>{console.log(this)}
t()
}
}
a.getName() // a
- 案例三 mdn的案例
// 创建一个含有bar方法的obj对象,
// bar返回一个函数,
// 这个函数返回this,
// 这个返回的函数是以箭头函数创建的,
// 所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,这反过来又设置了返回函数的值。
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar();
// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj); // true
// 但是注意,如果你只是引用obj的方法,
// 而没有调用它
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true
优先级
默认绑定 < 隐式绑定 < 显示绑定 <new绑定
// function foo() {
// console.log("foo:", this)
// }
// 比较优先级:
// 1.显式绑定绑定的优先级高于隐式绑定
// 1.1.测试一:apply高于默认绑定
// var obj = { foo: foo }
// obj.foo.apply("abc")
// obj.foo.call("abc")
// 1.2.测试二:bind高于默认绑定
// var bar = foo.bind("aaa")
// var obj = {
// name: "why",
// baz: bar
// }
// obj.baz()
// 2.new绑定优先级高于隐式绑定
// var obj = {
// name: "why",
// foo: function() {
// console.log("foo:", this)
// console.log("foo:", this === obj)
// }
// }
// new obj.foo()
// 3.new/显式
// 3.1. new不可以和apply/call一起使用
// 3.2. new优先级高于bind
// function foo() {
// console.log("foo:", this)
// }
// var bindFn = foo.bind("aaa")
// new bindFn()
// 4.bind/apply优先级
// bind优先级高于apply/call
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
bindFn.call("bbb")
总结
-
this的指向主要分为五种,抛开(with 和 eval),其中有四种属于 在'es5' 中就存在的,另一种是'es6'中才有的
1.1.作为对象的方法调用( 指向'对象')
1.2.作为普通函数调用(指向全局对象Window)
1.3. 构造器调用(构造函数中的this 指向是返回的对象)
1.4. 'Function.prototype.call' 或 'Function.prototype.apply' 调用
1.5. 箭头函数的'this'调用(es6) ('this' 指向是由最外层最近的一层非箭头函数)