不管是否在严格模式下,全局执行环境中(任何函数体外部)this
都指向全局对象html
var name = '以乐之名'; this.name; // 以乐之名
函数内部,this
的值取决于函数被调用的方式(被谁调用)前端
var name = '无名氏'; function getName() { console.log(this.name); } getName(); // 无名氏 调用者是全局对象 var myInfo = { name: '以乐之名', getName: getName }; myInfo.getName(); // 以乐之名 调用者是myInfo对象
"this的指向是在运行时进行绑定的,而不是代码书写(函数声明)时肯定!!!"git
"看谁用",this的指向取决于调用者,这也是不少文章提到过的观点。"谁调用,this指向谁",只是这句话稍有偏颇,某些状况不见得都适用。es6
生活栗子:你的钱并不必定是你的钱,只有当你使用消费了才是你的钱 。
("看谁用"),借出去的钱就不是你的了。。。github
回到正文,咱们先经过栈,来理解什么是调用位置?segmentfault
JavaScript中函数的调用是以栈的方式来存储,栈顶是正在运行的函数,函数调用时入栈,执行完成后出栈。数组
function foo() { // 此时的栈:全局 -> foo,调用位置在foo bar(); } function bar() { // 此时的栈:全局 -> foo -> bar,调用位置在bar baz(); } function baz() { // 此时的栈:全局 -> foo -> bar -> baz,调用位置在baz // ... } foo();
代码中虽然函数存在多层嵌套使用,但处于栈顶的只有正在执行的函数,也即调用者只有顶层的那一个(或最后一个),理清调用位置(调用者)有助于咱们理解this
。浏览器
call/apply/bind
)new
绑定(new
建立实例)this
会指向全局对象(浏览器全局对象是window
,NodeJS全局对象是global
);this
指向undefined
// 非严格模式 function getName() { console.log(this.name); // this指向全局对象 } getName(); // "",并不会报错,若是外部有全局变量name,则会输出对应值 // 严格模式 function getName() { "use strict" console.log(this.name); // this指向undefined } getName(); // TypeError: Cannot read property 'name' of undefined
TIPS: 严格模式中,对函数中this的影响,只在函数内声明了严格模式才会存在,若是是调用时声明严格模式则不会影响。前端工程师
function getName() { console.log(this.name); } // 调用时声明严格模式 "use strict"; getName(); // ""
隐式绑定中,函数通常做为对象的属性调用,带有调用者的执行上下文。所以this
值取决于调用者的上下文环境。若是存在多层级属性引用,只有对象属性引用链中最顶层(最后一层)会影响调用位置,而this
的值取决于调用位置。文章开头以栈来理解调用者的例子。app
function getName() { return this.name; } var myInfo = { name: '以乐之名', getName: getName }; var leader = { name: '大神组长' man: myInfo }; leader.man.getName(); // '以乐之名' // man 指向 myInfo,最顶层(最后一层)对象为 myInfo
apply/call
方法二者相似,均可以显示绑定this
,二者的区别是参数传递的方式不一样。apply/call
第一个参数都为要指定this
的对象,不一样的是apply
第二个参数接受的是一个参数数组,而call
从第二个参数开始接受的是参数列表。
apply语法:func.apply(thisArg, [argsArray])
call语法:func.call(thisArg, arg1, arg2, ...)
var numbers = [5, 6, 2, 3, 7]; // 求numbers的最大值 // apply var max = Math.max.apply(null, numbers); // call var max = Math.max.call(null, ...numbers); // ...展开运算符
TIPS: 若是thisArg为原始值(数字,字符串,布尔值),this
会指向该原始值的自动包装对象,如Number
, String
, Boolean
等
func.apply(1); // func中的this -> Number对象;
bind
是ES5新增的方法,跟apply/call
功能同样,能够显示绑定this。
bind语法:function.bind(thisArg[, arg1[, arg2[, ...]]])
bind()方法建立一个新的函数,在调用时设置this关键字为提供的值,并在调用新函数时,将给定参数列表做为原函数的参数序列的前若干项。
-- 《Function.prototype.bind() | MDN》
"bind与apply/call的区别:apply/call传入this并当即执行函数,而bind传入this则返回一个函数,并不会当即执行,只有调用返回的函数才会执行原始函数"。
bind
方法是函数柯里化的一种应用,看过上篇《前端进击的巨人(五):学会函数柯里化(curry) 》的小伙伴,应该还记得"函数柯里化的特色:延迟执行,部分传参,返回一个可处理剩余参数的函数"。
bind
相较apply/call
的优势,能够经过部分传参提早对this进行一次"永久绑定",也就是说this
只需绑定一次,省却每次执行都要进行this
绑定的操做。
function getName() { return this.name; } var myInfo = { name: '以乐之名', job: '前端工程师' }; var getName = getName.bind(myInfo); getName(); // '以乐之名'; getName(); // '以乐之名'; // 一次性绑定,以后调用无需再修改this
TIPS: 函数柯里化能够用于参数预设,像一次性操做(判断/绑定)等。
有关函数柯里化的详解,请回阅:《前端进击的巨人(五):学会函数柯里化(curry) 》。
经过new
操做符能够实现对函数的构造调用。JavaScript中自己并无"构造函数",一个函数若是没有使用new
操做符调用,那么它就是个普通函数,new Func()
其实是对函数Func
的"构造调用"。
在了解构造函数中的this
前,有必要先了解下new
实例化对象的过程。
__proto__
会指向函数的prototype
)this
会指向这个新对象,并对this
属性进行赋值return
,通常不会有return
)// 正常不带return的构造函数 function People(name, sex) { this.name = name; this.sex = sex; } var man = new People('亚当', '男'); var woman = new People('夏娃', '女'); // 实例化对象成功
// 构造函数带了return function People(name, sex) { return 1; // 返回的是Number对象 } function People(name, sex) { return 'hello world'; // 返回的是String对象 } function People(name, sex) { return function() {} } function People(name, sex) { return {}; } // 以上并未正确实例化对象
构造函数自定义return
,会形成new
没法完成正确的实例化操做。若是返回值为基本类型,则返回其包装对象Number/String/Bollean
。
TIPS: 原型链中的this指向其实例化的对象
People.prototype.say = function() { console.log(`个人名字:${this.name}`); }; var man = new People('亚当', '男'); man.say(); // 个人名字:亚当
显示绑定 / new
绑定 > 隐式绑定 > 默认绑定
TIPS: new
没法跟apply/call
同时使用
new
操做符使用(new
绑定)? YES --> this
绑定的是new
建立的新对象call/apply/bind
(显示绑定)? YES --> this
绑定的是指定的对象this
绑定的是那个上下文对象undefined
,不然指向全局对象箭头函数的this
机制不一样于传统的this
机制,它采起的是另一种机制,词法做用域的this
断定规则。
// 例子一 var name = '无名氏'; var myInfo = { name: '以乐之名', getName: () => { console.log(this.name); } }; var getName = myInfo.getName; window.getName(); // 无名氏 myInfo.getName(); // 无名氏 // myInfo是在全局环境定义的,所以根据词法做用域,this指向全局对象 // 例子二 var name = '无名氏'; var myInfo = { name: '以乐之名', say: () => { setTimeout(() => { console.log(this.name); }) } }; myInfo.say(); // 无名氏 // 箭头函数经过做用域链来逐层查找this,最终找到全局变量myInfo,this指向全局对象 // 例子三 var name = '无名氏'; var myInfo = { name: '以乐之名', say: function() { setTimeout(() => { console.log(this.name); }) } }; myInfo.say(); // 以乐之名 // 箭头函数找到say: function(){},所以this的做用域来自myInfo
TIPS: setTimeout/setInterval/alert
的调用者都是全局对象
"箭头函数的this
始终指向函数定义时的this
,而非执行(调用)时的this
。箭头函数中的this
必须经过做用域链一层一层向外查找,来肯定this
指向。"
// 函数表达式 const getName = (name) => { return 'myName: ' + name }; // 匿名函数 setTimeout((name) => { console.log(name); }, 1000)
()
;若是没有参数或多个参数需加括号()
// 只有一个参数 const getName = name => { return `myName: ${name}`; } // 无参数 const getName = () => { return 'myName: "以乐之名"'; } // 多参数 const getName = (firstName, lastName) => { return `myName: ${firstName} ${lastName}`; }
{}
const getName = name => return `myName: ${name}`;
{}
,可不写return,会自动返回const getName = name => `myName: ${name}`;
参考文档:
本文首发Github,期待Star!
https://github.com/ZengLingYong/blog
做者:以乐之名 本文原创,有不当的地方欢迎指出。转载请指明出处。