JavaScript中的this - 笔记

之前一直对this的指向很模糊,找了一些别人的博客看,又重新看了一下《你不知道的JavaScript》,感觉基本上是弄懂了,挑一些重点的地方记录一下,有些地方对我来说书上解释写的不够多,所以自己做下补充以方便理解,有理解错的地方还望指出。

一.澄清误区



首先你需要知道:

1.this并不指向函数自身

2.this的作用域在任何情况下都不指向函数的词法作用域。

举个例子:

function foo() {
    var a = 2;
    this.bar();
}

function bar() {
    console.log(this.a);
}

foo();//ReferenceError: a is not defined

第一次看这里的时候就掉坑里了,想当然的以为foo()中this指向window,而bar()又在全局中故能调用bar(),但实际上这是错的,不能使用this来引用一个词法作用域内部的东西。

this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用。跟函数声明的位置没有任何关系,要区别于函数的作用域,函数的作用域是在它被定义的时候确定的。

要判断一个运行中函数的this绑定,需要找到这个函数的直接调用位置。

二.调用位置



调用位置就是函数在代码中被调用到的位置。

this有四条绑定规则:

1.默认绑定:非严格模式下this指向全局对象,严格模式绑定到undefined。 var bar = foo()

2.隐式绑定:this指向包含它的函数的对象,要注意隐式丢失的情况。 var bar = obj1.foo()

3.显示绑定:this指向call()、apply()和bind()方法指定的对象。 var bar = foo.call(obj2)

4.new绑定:this指向构造的新对象。 var bar = new foo()

优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

具体看下面:

1.默认绑定

无法应用其他规则时默认使用默认绑定。如果函数独立调用,使用默认绑定。

function foo() {  //看函数体是否处于严格模式,是看这个位置("use strict")
  console.log(this.a);
}

var a = 2;

foo(); //2  仅foo()函数本身,为独立调用

在上面的代码中,foo()是使用不带任何修饰的函数引用进行调用的,所以会使用默认绑定,非严格模式下this指向全局对象,严格模式绑定到undefined。不是指调用位置是否处于严格模式,而是函数体是否处于严格模式。

2.隐式绑定

考虑函数的调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo  //foo()被当作引用属性添加到obj中,此时它被obj对象包含,此时this指向obj
}

obj.foo(); //2

对象属性引用链中只有最后一层会影响调用位置。

function foo() {
  console.log(this.a);
}

var obj2 = {
  a: 42,
  foo: foo
}

var obj1 = {
  a: 2,
  obj2: obj2
}

obj1.obj2.foo();  //42  虽然这里有obj1对象和obj2对象,但是obj2才是处于最后一层最接近foo(),所以会指向obj2

隐式丢失的情况

来看下面的代码:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;  //虽然bar是obj.foo的一个引用,但实际上bar引用的只是foo函数本身,可以看成bar() = foo(),此时foo()是独立调用故绑定到全局对象
var a = "oops, global";

bar(); //oops, global  

还有一种常见的隐式丢失的情况是传入回调函数

function foo() {
  console.log(this.a);
}

function doFoo(fn) {
  fn();
}

var obj = {
  a: 2,
  foo: foo
}

var a = "oops, global";

doFoo(obj.foo); //oops, global  obj.foo作为参数传入实际上隐式赋值给了fn,可以看成fn = obj.foo,                //此时又回到了上一例代码的情况,引用的是foo函数本身,看成fn()= foo(),独立调用指向全局对象

3.显示绑定

使用函数的call()和apply()方法可以使用显示绑定,这两个方法的第一个参数都是一个对象,他们会把这个对象绑定到this,在调用函数时指定这个this,因为可以指定绑定对象故称做显示绑定。

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2
}

foo.call(obj);

但是这种显示绑定没有解决隐式丢失的问题,要解决这个问题可以使用硬绑定。

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2
}

var bar = function() {
  foo.call(obj);
}

bar(); //2

setTimeout(bar, 100) //2

bar.call(window); //2

创建了一个函数bar(),并在它的内部手动调用foo.call(obj),因此强制把foo的this绑定到了obj,之后无论如何调用函数bar,它总会手动在obj上调用foo,这叫做硬绑定。

硬绑定非常常用,ES5中提供了内置的方法Function.prototype.bind,bind()会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。

硬绑定的两个典型应用场景:

  • 创建一个包裹函数,传入所有的参数并返回接收到的所有值
function foo(something) {
  console.log(this.a, something);
  return this.a + something; //返回接收到的所有this.a和something
}

var obj = {
  a: 2
}

var bar = function() {
  return foo.apply(obj, arguments); //this指向obj,arguments为传入的参数3
}

var b = bar(3); //2 3
console.log(b); //5
  • 创建一个可以重复使用的辅助函数
function foo(something) {
  console.log(this.a, something);
  return this.a + something;
}

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}

var obj = {
  a: 2
}

var bar = bind(foo, obj);
var b = bar(3); //2 3
console.log(b); //5

4.new绑定

使用new来调用函数,或者说发生构造函数调用时,会执行下面的操作:

①创建(构造)一个全新的对象。

②这个新对象会被执行[[原型]]连接。

③这个新对象会绑定到函数调用的this。

④如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(a) {
  this.a = a;
}

var bar = new foo(2);//使用new操作符调用foo(),构造了一个新对象bar并把它绑定到foo()的this上
console.log(bar.a); //2

三.绑定例外

有一些例外情况需要注意

1.把null或undefined作为this的绑定对象传入call、apply或bind。

null和undefined在调用时会被忽略,然后应用默认绑定。

function foo() {
  console.log(this.a);
}

var a = 2;

foo.call(null);//2

有时你可能选择null作为一个占位值而选择null作为参数,但是总是用null来忽略this的绑定可能产生一些副作用。一种更安全的做法是传入一个空的非委托对象,把this绑定到这个对象不会对你的程序产生任何副作用。

2.无意间创建了一个函数的“间接引用”

同样会应用默认绑定,最容易在赋值时发生。

function foo() {
  console.log(this.a);
}

var a = 2;
var o = {
  a: 3,
  foo: foo
}
var p = {
  a: 4
}

o.foo();//3
(p.foo = o.foo);//2 p.foo = o.foo的返回值是目标函数的引用,故调用的位置是foo(),又会应用默认绑定

3.软绑定 softBind()

硬绑定会降低函数的灵活性,使用硬绑定后就无法使用隐式绑定或显示绑定来修改this。

软绑定:给默认绑定指定一个全局对象和undefined以外的值,可以实现和硬绑定相同的效果,同时保留隐式绑定或显示绑定修改this的能力。

除了软绑定外,softBind()的其他原理和bind()类似。首先检查调用时的this,如果this绑定到全局对象或者undefined,就把指定的对象绑定到this,否则不会修改this。

function foo() {
  console.log("name:" + this.name);
}

var obj = {
  name: "obj"
}
var obj2 = {
  name: "obj2"
}
var obj3 = {
  name: "obj3"
}

var fooOBJ = foo.softBind(obj);

fooOBJ();//obj

obj2.foo = foo.softBind(obj);
obj2.foo();//obj2 obj2.foo()调用时,this绑定到obj2上,不是全局对象也不是undefined,所以不会调用指定的obj,而是使用原来的obj2。

fooOBJ.call(obj3);//硬绑定时绑定了obj3,后面不可再修改

setTimeout(obj2.foo, 10);//假设setTimeout(fn,10),fn实际上引用的只是foo(),可以看成fn = obj2.foo,类似前面的隐式绑定丢失,                         //会使用默认绑定到全局,这时会发生软绑定,绑定到指定对象obj

4.箭头函数

箭头函数无法使用this中的四种绑定规则,而是根据外层(函数或全局)作用域来决定this。

function foo() {
  return (a) => {
    console.log(this.a);
  }
}

var obj1 = {
  a: 2
}

var obj2 = {
  a: 3
}

var bar = foo.call(obj1);
bar.call(obj2);//2 箭头函数会捕获调用时foo()的this,foo()的this会显示绑定到obj1,               //bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改

箭头函数常用于回调函数中,例如事件处理器或者定时器

function foo() {
  setTimeout( () => {
    console.log(this.a);//箭头函数会继承外层函数调用的this绑定,这里箭头函数的外层函数是foo(),foo()的this绑定到obj,所以箭头函数的this也绑定到obj。
  }, 100);
}

var obj = {
  a: 2
}

foo.call(obj);//2
时间: 2024-11-25 12:48:52

JavaScript中的this - 笔记的相关文章

JavaScript中正则表达式学习笔记

一.正则表达式是什么: 处理字符串时,经常需要处理复杂规则的字符串.正则表达式就是用于描述这些规则的工具.换句话说,就是记录文本规则的代码. 二.正则表达式能做什么: 数据有效性验证(测试字符串匹配)如邮箱,电话号码等 替换文本 提取子字符串 三.特点: 灵活性.逻辑性.功能性非常强 可以迅速的用极简单的方法达到字符串的复杂控制 刚接触的人比较晦涩难懂 四.规则 1.普通字符 作用:匹配与之相同的一个字符. 字母,数字,汉字,下划线. 2.非打印字符 \f:匹配一个换页符.等价于 \x0c 和

你不知道的JavaScript中,读书笔记

七种内置类型 null, undefined, boolean, number, string, object, symbol typeof null === 'object' // true null 是 typeof 是 object 的唯一的假值 typeof function 会返回 'function' 使用 typeof x !== 'undefined' 比直接判断 x 更加安全,因为不会引发 reference error

关于Javascript中通过var关键字声明变量和function关键字声明函数的笔记

一.概念 1.变量声明 在JavaScript中,变量一般通过var关键字(隐式声明,let关键字声明除外)进行声明,如下通过var关键字声明a,b,c三个变量(并给其中的a赋值): var a=1,b,c; //关键字显式声明变量a,b,c,并给a赋值console.log(a); //1 //由于b,c未定义变量类型,因此输出"undefined"console.log(b); //undefinedconsole.log(c); //undefined //如果变量未声明,则输出

JavaEE笔记——JavaScript中对dom的操作

节点及其类型 在JavaScript中,节点分为三种: 元素节点:HTML标签元素. 属性节点: 元素的属性, 可以直接通过属性的方式来操作. 文本节点: 是元素节点的子节点, 其内容为文本. 在 html 文档的什么位置编写 js 代码? 一般地, 在 body 节点之前编写 js 代码, 但需要利用 window.onload 事件, 该事件在当前文档完全加载之后被触发, 所以其中的代码可以获取到当前文档的任何节点 <head> <meta http-equiv="Cont

JavaScript 中的内存和性能、模拟事件(读书笔记思维导图)

由于事件处理程序可以为现代 Web 应用程序提供交互能力,因此许多开发人员会不分青红皂白地向页面中添加大量的处理程序.在 JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能.导致这一问题的原因是多方面的.首先,每个函数都是对象,都会占用内存:内存中的对象越多,性能就越差.其次,必须事先指定所有事件处理程序而导致的 DOM 访问次数,会延迟整个页面的交互就绪时间.从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的. 事件经常由用户操作或通过其他浏览

javascript中的匿名函数整理笔记

以下为总结在开源的JavaScript框架中能看到很多这样语法结构(function(){})()比如我最近看的jQuery,及chediter.刚开始的时候我看到这样的结果有点奇怪,它是怎么执行的,并且这是什么样的语法结构,最近偶尔看闭包的时候,才发现原来这是JavaScript种匿名函数(看到这个有点汗,java的匿名类见过,就从来没想到JavaScript中会有匿名函数,也是学的不够牢固).现在我们了解到以上是JavaScript匿名函数的语法结构,怎么声明函数,匿名函数JavaScrip

理解javascript中的原型模式

一.为什么要用原型模式. 早期采用工厂模式或构造函数模式的缺点:  1.工厂模式:函数creatPerson根据接受的参数来构建一个包含所有必要信息的person对象,这个函数可以被无数次的调用,工厂模式尽管解决了创建多个相似对象的问题,却没有解决对象识别的问题(返回的是自定义的一个对象o,不明确对象o的类型).  2. 构造函数模式:构造函数的调用和其他oo语言一样,用new操作符来构建一个Person的实例:javascript中的构造函数也是函数(所以也可以直接像普通函数那样直接调用方法名

JavaScript中的防篡改对象

由于JavaScript共享的特性,任何对象都可以被放在同一环境下运行的代码修改. 例如: var person = {name:"caibin'} person.age = 21; 即使第一行定义了完整的person对象,那么第二行代码仍然可以对其添加属性,删除属性等. 我们有三个方法可以防止你做出这些行为. 一.不可扩展对象: 先来看person本身的扩展性: Object.isExtensible(person); // true 接下来执行: Object.preventExtensio

JavaScript高级程序设计学习笔记--基本概念

1.语句 ECMAScript中的语句以一个分号结尾:如果省略分号,则由解析器确定语句的结尾,如下例所示: var sum=a+b //即使没有分号也是有效的语句--推荐 var diff=a-b; //有效的语句--推荐 虽然语句结尾的分号不是必需的,但我们建议任何时候都不要省略它.两个原因:1.加上分号可以避免很多错误 2.加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间 推测应该在哪里插入分号了. 2.变量 var message="hi"; 像这样初始化变量