这篇文章要来介绍三种会影响 this 的方法
这是我们一开始的程式码
var myName = '真心镇大冒险'; var family = { myName: '小明家'};function fn (para1, para2) { console.log(this, para1, para2);}
那么我们要介绍的是 call, apply, bind 这三种方法,来改变 this
的指向
这三个的方法观念都很相近,只是呼叫方法上不太一样,我们来看看上一个章节我们提到的 简易呼叫(Simple Call),利用这个 fn 的函式印出来的结果是如何?
var myName = '真心镇大冒险'; var family = { myName: '小明家'};function fn (para1, para2) { console.log(this, para1, para2);}fn(1, 2); // 简易呼叫(Simple Call)
我们可以看到运用 简易呼叫(Simple Call) 的方式, this
的指向当然是指向 Window 物件。并且传入的参数是 1 跟 2,这个应该没甚么问题。
call
fn.call(family, 1, 2);// fn.call(要让 this 指向的对象, 参数1, 参数2);
这边再要执行的function后面接上 .call()
,并且第一个参数是给予要让前面的 function 执行环境中的 this 要指向哪个对象,通常是给予物件型别的资料,再来后面就是依据传入该 function要传入的参数。
所以结果会是
那么在使用 call 的时候要特别注意,他是立刻执行这个函式,跟我们利用简易呼叫(Simple Call)的方式有点像,主要的差别就是可以透过 call 改变 this 的指向。
apply
apply 的跟 call 很像,只是传入参数给 function 的形式不太一样
fn.apply(family, [3, 4]);// fn.apply(要让 this 指向的对象, [参数1, 参数2]);
使用 apply 方法的时候,要传入的参数必须包在一个阵列里面,当作第二个参数传入。
同样也是立即执行,第一个参数也是用来改变执行环境的 this 指向。
bind
bind的方法跟call, apply的主要差异在于,他不会立刻执行前面的 function,因此要使用bind的方法的时候呢,必须先做一些处理。
var fn2 = fn.bind(family, '小明', '杰伦');fn2();
先把 fn.bind(family, '小明', '杰伦')
的内容用变数盛装以后,再利用执行函式的方式执行这个变数。
那么在执行的过程中,就会自动替换 function 的 this
指向。
那么在 fn2() 的小括号中带入参数的话会怎么样吗?
答案是不会,因为我们一开始在定义 fn2 的时候就已经定义好要带入的参数了。
但是如果一开始没有定义好带入的参数的话呢?
var fn2 = fn.bind(family, '小明');fn2(1, 2);
这样的话,少了一个参数,但我又在fn2的小括号中带入1跟2的参数,那么会用哪个参数呢?
答案是会从 fn2 传入的第一个参数当作是 fn 的第二的参数,依序补足不够的参数。
另外一个要注意的是,虽然 fn2()
这样的形式是 简易呼叫(Simple Call),但是 fn 的 this 在一开始的时候就被指定了,所以这边就算是用简易呼叫(Simple Call)的方式, fn 的this 还是会被 bind 绑定给指定的物件。
进阶观念
到目前为止,我们知道可以透过 call, apply, bind 来改变 this 的指向。
那么如果是下面这样的状况,会发生甚么事情呢?
fn.call(1, '小明', '杰伦');
如果是传入其他非物件型别的原始型别资料,会自动被转换成建构式方式的物件型别。
fn.call('文字', '小明', '杰伦');
我们再将 fn 多加上一个 typeof(this)
var myName = '真心镇大冒险'; var family = { myName: '小明家'};function fn (para1, para2) { console.log(this, typeof(this), para1, para2);}
存档之后的结果就是
那我们再来传一次不同的值看看
fn.call(undefined, '小明', '杰伦');
没想到传入了 undefined,居然会直接被指向到 window?!?!?1
为甚么呢?我们来看一下 MDN的文件 怎么说:
也就是说,如果再 非严格模式 的状态下,就会将传入的非物件型别资料封装,也就是利用建构式封装。
并且如果传入的是 null undefined的话,就会被置换成是全域变数。
喔喔~这样应该就能够了解为什么刚刚传入的是 undefined。
但后来 this 会指向为 window了。
但到底甚么是严格模式阿???
严格模式
因为 Javascript 是相对宽鬆的程式,因此有很多不严谨的程式码也能够运行,但在维护上就会相对麻烦,并且不容易除错。
在 ES5 之后就提供了开发者这种语法受限的模式,那么这个模式下呢就有以下这些特点
严格模式呢,只要在程式码中加入 'use strict' 的字串就可以。
主要是因为只撰写 'use strict' 的字串 是属于一种表达式,那么这个程式码不会影响不支援严格模式的浏览器。
并且可以加在特定的函式内进行严格模式,也就是说只有在特定的大括号内中执行的程式码才会套用严格模式。当然如果在全域的环境使用的话,你所有的程式码都会进入严格模式喔!
(function () { 'use strict'; // 这个执行环境进入了严格模式})();
那么在严格模式中,透过抛出错误的方式来提醒开发者一些安静的错误。也可以避免掉一些开发的不好习惯,同时也有利于侦错。
其中一个例子就是,如果在严格模式下,没有使用 var let const 等宣告变数的方式进行宣告的话,他会直接跳错。
在严格模式下呢,他不允许你直接对变数赋予值,他会要你先宣告这个变数之后,再对变数赋予值。
那么其他更详细,有关严格模式的内容,可以参考 MDN的文件
不过要特别注意的是,在严格模式的状态下,每个浏览器的结果可能会有不同,所以有时候文件跟实际的状况有出入也是属于正常的。要特别有心理準备~XDD
那么接着我们再来看一下严格模式的其他例子吧~
function callStrict (para1, para2) { 'use strict'; console.log(this, typeof(this), para1, para2);}callStrict.call(1, '小明', '杰伦');
这样会发生甚么事情呢?
可以看到在严格模式下,this 的指向就是我们透过 call 所传入的 数字1。
并且没有被封装成建构式的形式!
那再来看看传入 undefined 给大家看看
callStrict.call(undefined, '小明', '杰伦');
原本我们再非严格模式(sloppy mode)下,传入 undefined 的话就会将 this 指向全域的window 物件。
那么再严格模式下,传入 undefined 的时候就还是会维持 undefined。
那么我如果简易呼叫 callStrict 的话会怎么样呢?
callStrict('小明', '杰伦');
那么为什么会这样呢?
其实你使用简易呼叫的时候,就等于是直接套用call这个方式进行呼叫。只是我们第一个参数没有传入,所以是 undefined。
那么如果再严格模式下的话,当然就会是 undefined;如果是非严格模式(sloppy mode)下,虽然会自动将 this 自动指向到 window 物件,可是它的本质其实是 undefined。
这也是为什么前面说的,在使用简易呼叫的时候,尽量不要去调用他的 this, 因为它的本质其实是 undefined。
好~那么这篇文章就介绍到这边,没问题就往下继续看下去吧~!汪汪