前言:
想要学好前端,基本功必须得要扎实,原型和原型链、闭包、this,那这三座大山就一定要跨过去,今天先说this,话不多说,go~
this:
在开发时,我们要搞清楚this的指向,是至关重要的~
为了能够一眼看出this指向的是什么,我们需要确定它的绑定规则是哪个?this有四种绑定规则:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定
无法应用其他规则时就应用默认绑定,经常是独立函数调用
function foo(){
console.log(this.a);
}
var a = 3;
foo();//3
上面函数就应用了this的默认绑定,foo()前面没有调用它的对象,其实也可以这么想,window.foo(),window可以省略,所以foo()里面的this指向全局对象window(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
隐式绑定
考虑调用位置是否有上下文对象
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
obj.foo();//2
因为调用foo的时候前面加上了obj,所以隐式绑定会把函数调用中的this绑定到这个上下文对象,也就是obj,所以this.a 和obj.a是一样的
但是,你需要记住一句话:this永远指向最后调用它的那个对象
function foo(){
console.log(this.a);
}
var obj1 = {
a:2,
foo:foo
}
var obj2 = {
a:3,
obj1:obj1
}
obj2.obj1.foo();//2
可见,结果是2,因为this指向最后调用它的那个对象,即obj1
在使用隐式绑定的时候,有一个常见的问题,就是会出现隐式丢失
隐式丢失
也就是说出现了隐式丢失,会应用默认绑定的规则
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
var baz = obj.foo;
var a = '我是全局对象的a';
baz();//我是全局对象的a
因为baz()其实是一个独立函数调用,所以应用了默认绑定
还有一种更常见的情况会发生隐式丢失,那就是在传入回调函数时:
function foo(){
console.log(this.a);
}
function baz(fn){
fn()
}
var obj = {
a:2,
foo:foo
}
var a = '我是全局对象的a';
baz(obj.foo);//我是全局对象的a
参数传递其实就是一种隐式赋值
显示绑定
所谓显示,是因为你可以直接指定this的绑定对象,我们可以借助apply,call,bind等方法
apply和call作用一样,只是传参的方式不同,都会执行对应的函数,但是bind不一样,它不会执行,需要手动去调用
function foo(){
console.log(this.a);
}
function baz(fn){
fn()
}
var obj = {
a:2,
}
foo.call(obj);//2
foo.apply(obj);//2
但是,依然无法解决丢失绑定的问题,如下:
function sayHi(){
console.log('Hello,', this.name);
}
var per = {
name: 'mengyun',
sayHi: sayHi
}
var name = 'anna';
var Hi = function(fn) {
fn();
}
Hi.call(per, per.sayHi); //Hello, anna
但是我们可以给fn也硬绑定this,就可以解决这个问题
function sayHi(){
console.log('Hello,', this.name);
}
var per = {
name: 'mengyun',
sayHi: sayHi
}
var name = 'anna';
var Hi = function(fn) {
fn.call(this);
}
Hi.call(per, per.sayHi); //Hello, mengyun
原因:因为per被绑定到Hi函数中的this上,fn又将这个对象绑定给了sayHi的函数。这时,sayHi中的this指向的就是per对象。
上面代码用bind可以改写成:(bind在你不知道的js(上)中就是被归为硬绑定)
function sayHi(){
console.log('Hello,', this.name);
}
var per = {
name: 'mengyun',
sayHi: sayHi
}
var name = 'anna';
var Hi = sayHi.bind(per);
Hi.call(per, per.sayHi); //Hello, mengyun
new绑定
js跟其他语言不一样,没有类,所以我们可以用构造函数来模拟类
使用new来调用函数,会自动执行下面的操作:
- 创建一个全新的对象
- 这个对象会被执行[[Prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么返回这个新对象,否则返回构造函数返回的对象
手写一个new
function _new(fn,...args){
//1、创建一个空对象
//2、这个对象的 __proto__ 指向 fn 这个构造函数的原型对象
var obj = Object.create(fn.prototype);
//3、改变this指向
var res = fn.apply(obj,args);
// 4. 如果构造函数返回的结果是引用数据类型,则返回运行后的结果,否则返回新创建的 obj
if ((res!==null && typeof res == "object") || typeof res == "function") {
return res;
}
return obj;
}
下面我们用new来绑定一下this,
function Person(name){
this.name = name;
}
var per = new Person('mengyun');
console.log(per.name);//mengyun
此时per已经被绑定到Person调用中的this上
优先级
在知道了四种绑定规则之后,我们需要的就是准确判断是哪种绑定规则,但是如果某个调用位置可以应用多条规则,那么就需要有个优先级了。 毫无疑问,默认绑定是最低的,可以先不考虑他,下面来看个例子:
function f1(){
console.log(this.a);
}
var obj1 = {
a:2,
f1:f1
};
var obj2 = {
a:3,
f1:f1
};
obj1.f1();//2
obj2.f1();//3
obj1.f1.apply(obj2);//3
可以看出来,显示绑定优先级大于隐式绑定
其他的demo大家可以自行去写,最后的结论就是
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
绑定例外
如果把null或者undefined作为绑定对象传入call,apply,bind,这些值往往会被忽略,实际应用的是默认绑定规则:
var obj= {
name: 'mengyun'
}
var name = 'Anna';
function bar() {
console.log(this.name);
}
bar.call(null); //Anna
注意:
但是以上规则只能应用在普通函数上,箭头函数例外,因为它没有自己的this,而是根据外层作用域来决定的,而且箭头函数的绑定无法被修改
。
function foo(){
return (a)=>{
console.log(this.a);
}
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var baz = foo.call(obj1);
baz.call(obj2);//2
由于foo()的this被绑定到obj1,所以baz()也被绑定到obj1,并且无法被修改
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!