时间: 2019-10-14阅读: 76标签: new
关于this
与静态词法作用域不用,this的指向动态绑定,在函数执行期间才能确定。感觉有点像C++的多态?
var a = 1;
var obj = {
a: 2,
fn: function() {
console.log(this.a);
}
};
obj2 = {
a: 3,
fn: obj.fn
};
//通过对象调用 this指向obj
obj.fn(); //2
//通过函数调用 this指向window
setTimeout(obj.fn, 0); //1
obj2.fn(); //3
这个例子很好理解,谁调用的函数,this就指向谁。
当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪里被调用,函数的调用方法,传入的参数等信息。this就是记录的其中一个信息。
开始详解this了!
理解this绑定,首先要理解调用位置:调用位置就是函数在代码中被调用的位置。
分析位置最重要的是分析调用栈,我们关心的调用位置就在当前正在执行的函数的前一个调用中。
//调用栈是f1 即全局作用域
function f1() {
console.log('f1');
f2(); //f2调用位置
}
//调用栈是f1 => f2
function f2() {
console.log('f2');
f3(); //f3调用位置
}
//调用栈是f1 => f2 => f3
function f3() {
console.log('f3');
}
f1(); //f1调用位置
通过找到调用位置,判断需要应用哪一条规则。this绑定的四条规则:
默认绑定
最常用的函数调用类型:独立函数调用。
var a = 1;
// 全局函数
function fn() {
// 'use strict' error
console.log(this.a); //1
}
//默认this绑定到window
fn();
在这里,fn是直接进行调用,没有任何修饰,相当于window.fn(),只能适用默认绑定,指向了window。
隐式绑定
这种绑定需要考虑调用位置是否有上下文对象。
function fn() {
console.log(this.a);
}
var obj = {
a: 2,
fn: fn
};
obj.fn(); //2
当fn被调用时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用fn时this被绑定到obj,因此this.a和obj.a是一样的。
另外,链式调用时,只有最近的调用对象会影响this。
var a = 1;
var obj = {
a: 2,
fn: function() {
console.log(this.a);
}
};
obj2 = {
a: 3,
fn: obj
};
//this.a相当于obj.a
obj2.fn.fn(); //2
隐式丢失
一个最常见的this绑定问题是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或undefined上。
function fn() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
fn: fn
}
var f = obj.fn;
obj.fn(); //2
f(); //1
被作为函数调用时,会被默认绑定到window对象。
另外一种情况是传入回调函数时:
function fn() {
console.log(this.a);
}
function fn2(fn) {
fn();
}
var a = 1;
var obj = {
a: 2,
fn: fn
}
//还是作为函数调用
fn2(obj.fn);
显式绑定
就是用apply和call方法强制绑定this。一个例子说明一切:
var a = 1;
var obj = {
a: 2,
fn: function() {
console.log(this.a);
}
};
obj2 = {
a: 3,
fn: obj.fn
};
obj.fn.call(this); //1
obj.fn.call(obj); //2
obj.fn.call(obj2); //3
硬绑定
直接用vue源码来举例子吧。
function bind(fn, ctx) {
return function(a) {
var len = arguments.length;
return len ? len > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx);
}
}
简单暴力,都会背了。
new绑定
JS中new的机制实际上和面向类的语言完全不同。
在JS中,构造函数只是一些使用new操作符时被调用的函数,它们不属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
Number作为构造函数时,ES5.1中这样描述:当Number在new表达式中被调用时,它是一个构造函数,会初始化新创建的对象。
因为,包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。实际上不存在所谓的构造函数,只有对于函数的构造调用。
使用new来调用函数,会自动执行下面的操作:
1、创建一个全新的对象。
2、这个新对象会被执行【原型】连接。
3、这个新对象会绑定到函数调用的this。
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function fn(a, b) {
this.a = a;
this.b = b;
}
var f = new fn();
console.log(f); //{a:undefined,b:undefined}
那么什么是this,new,bind,call,apply呢?这些你都用过吗?掌握这些内容都是基础中的基础了。如果你不了解,那还不赶快去复习复习,上网查阅资料啥的!
优先级
如果某个调用位置应用多条规则,需要为这些规则设定优先级。(默认绑定、隐式绑定、显示绑定、new绑定)
首先默认绑定优先级最低。
隐式 VS 显示?
function fn() {
console.log(this.a);
}
var obj1 = {
a: 2,
fn: fn
};
var obj2 = {
a: 3,
fn: fn
}
obj1.fn(); //2
obj2.fn(); //3
obj1.fn.call(obj2); //3
obj2.fn.call(obj1); //2
显式比较厉害。
再来比较一下new和显示绑定优先级:
function fn(a) {
this.a = a;
}
var obj = {};
//this强制绑定到obj对象上
var f1 = fn.bind(obj);
f1(2);
//现在obj有了一个a属性
console.log(obj.a); //2
//new一个对象将this.a改成3
var f2 = new f1(3);
//这里依然没有变
console.log(obj.a); //2
//将this指向了new出来的a
console.log(f2.a); //3
可以看出,通过new的绑定,覆盖了bind强制绑定的a,所以new绑定是优先于显示绑定的。
顺便贴一下MDN提供的bind()实现:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying ' +
'to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(
this instanceof fNoP && oThis ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
通过call,apply,bind可以改变this的指向,this指向一般指向它的调用者,默认挂载在window对象下。es6中的箭头函数中,this指向创建者,并非调用者。
总结
优先级为:
1、如果使用了new,this绑定新创建的对象。
2、通过call、apply显式绑定。
3、是否使用隐式绑定?即对象调用
4、其余情况默认绑定在window上
const func = function () { console.log(this); const func2 = function () { console.log(this); }; func2(); //Window }; func(); //Window
'use strict' const func = function () { console.log(this); const func2 = function () { console.log(this); }; func2(); //undefined }; func(); //undefined
var a = 2var obj = { a: 4, foo:() = { console.log(this.a) function func() { this.a = 7 console.log(this.a) } func.prototype.a = 5 return func }}var bar = obj.foo() // 浏览器中输出: 2bar() // 浏览器中输出: 7new bar() // 浏览器中输出: 7
例外情况
1、如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用默认绑定规则(window)。
如果用apply展开数组可能用得上,比如fn.apply(null,arr),其中arr为数组,里面的值作为参数传给fn。
但是这个参数十分不安全,可能会不经意的修改全局变量,可以通过纯空对象来创建一个安全的this。
var a = 1,
b = 1;
console.log(a); //1
function fn(a) {
this.a = a;
}
function fn2(b) {
this.b = b;
}
//原本可能只想利用apply
fn.apply(null, [2]);
//不小心改了全局变量!
console.log(a);
//创建一个纯空对象
var obj = Object.create(null);
console.log(b); //1
//安全的this
fn2.call(obj, 3);
// 现在安全了!
console.log(b); //1
但是引入了箭头函数,问题就有点奇怪了。
var a = 1;
var obj = {
a: 2,
fn: () => {
console.log(this.a);
}
};
obj2 = {
a: 3,
fn: obj.fn
};
obj.fn(); //1
setTimeout(obj.fn, 0); //1
obj2.fn(); //1
全是1,这是怎么回事?换arguments的例子来看:
function fn() {
setTimeout(function() {
console.log(arguments);
}, 0)
}
fn(1, 2); //[] 确实未传入参数
function fn2() {
setTimeout(() => {
console.log(arguments);
}, 0)
}
fn2(1, 2); //[1,2] 把外部的参数吸进来了!
简单来讲,箭头函数使得this查询回到了词法作用域规则,它本身不具有this、arguments等属性,根据外层作用域来决定this。
function fn() {
return (a) => console.log(this.a);
}
var obj = {
a: 1
};
var obj2 = {
a: 2
}
var f = fn.call(obj);
f(); //1
f.call(obj2); //1
当箭头函数外部的fn被绑定到obj时,无论怎么调用和绑定,this指向永远是上一层作用域(window),所以会输出1。
一般函数方法中使用this指向全局对象:
真·总结
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面4条规则来判断this的绑定对象。
1、由new调用?绑定到新创建的对象。
2、由call、apply或bind调用?绑定到指定对象。
3、由上下文调用?绑定到那个上下文调用。
4、默认:严格模式绑定到undefined,否则绑定到全局对象。
function test(){ this.x = 1 console.log(this.x)}test() // 1
作为构造函数调用,this指向new实例化的对象:
function test(){ this.x = 1}var o = new test()alert(o.x) // 1
第一,this的学习,需要掌握哪些呢?this最重要的就是其指向的类型,那么在JavaScript中应该如何确定this的指向呢?
this是在函数被调用时确定的,它的指向完全取决于函数调用的地方,而不是它被声明的地方。(除了箭头函数)记住一点:this始终指向调用它的对象,对象中的方法中的this,指向调用它的对象。
var obj = { a: 1, b: { a: 2, func: function() { console.log(this.a); // 输出结果为2 console.log(this); // 输出结果是b对象 } }}// 调用obj.b.func();
var obj = { a: 1, b: { func: function() { console.log(this.a); // undefind console.log(this); // b对象 } }}// 调用obj.b.fun();
改变调用方法,不直接调用:
var obj = { a: 1, b: { a: 2, func: function() { console.log(this.a); // undefined 若在对象obj外定义a,则输出的就是其在外定义的值 console.log(this); // window } }}var j = obj.b.func; // 只是将b对象下的方法赋值给j,并没有调用j(); // 调用,绑定的对象是window,并非b对象直接调用
在绝大多数情况下,函数的调用方式决定了this的值,this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。this指向的对象称为函数的上下文对象context,this的指向取决于函数被调用的方式。
function foo() { console.log(this);}
那么我来问你?this指向哪里?哈哈哈,应该时不知道吧,因为谁调用指向谁,函数都没被调用,确实不知道指向。
function foo() { console.log(this);}// window全局对象 undefined// obj对象var obj = { foo: foo}obj.foo(); {foo:f}
直接通过函数名来调用函数,this指向全局变量window,通过对象,函数名调用函数,this指向该对象。当一个函数被调用的时候,会创建一个执行的上下文,它包含函数在哪里被调用,函数的调用方式,传入的参数等信息。
this的使用场景:
作为构造函数被new调用,作为对象的方法使用,作为函数直接调用,被call,apply,bind调用,箭头函数中的this。
基础:函数内部this指向问题:
var myObj = { foo: "bar", func: function() { var self = this; console.log(this.foo); console.log(self.foo); (function() { console.log(this.foo); console.log(self.foo); }()); }}myObj.func();
结果:
barbarundefinedbar
在对象方法中调用时:
var location = { x: 0, y: 0, move: function(x,y) { this.x = this.x + x; this.y = this.y + y; console.log(this.x); // 1 console.log(this.y); // 1 }};location.move(1,1) // this绑定到当前对象,即为location对象
本文由10bet发布于Web前端,转载请注明出处:读书笔记-你不知道的JS上-this
关键词: