function foo() {
console.log( a );
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();

上面这段代码为什么会输出2,而不是3?因为javaScript 只有词法作用域,foo()的定义在全局作用域,执行时会在它所在词法作用域查找变量a

this 的作用域

function foo() {
var a = 2;
this.bar();
console.log(this)
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined

说明 this并不指向foo函数的词法作用域,在非严格模式的node环境下打印console.log(this) ,发现this指向global 全局对象

Object [global] {
global: [Circular],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(util.promisify.custom)]: [Function]
}
}

而在严格模式下thisundefined

'use strict'
function foo() {
var a = 2;
console.log(this) //undefined
}
foo();

由此可以猜测this 可以用来绑定函数执行时的上下文,所谓上下文就是函数调用所要的各种环境变量等,在非严格模式下node 的全局上下文是global 而浏览器是windowthis 不遵循词法作用域,而是作用在函数执行的时候。

this 绑定规则

1、默认绑定,this 指向全局对象

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

2、隐试绑定this 指向obj对象

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); //

隐试绑定丢失

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo;
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

foo 并不属于obj对象,将obj.foo; 赋值给bar变量,实际上这是一个声明在全局的表达式,这时调用bar(), this 在非严格模式下默认指向全局对象

3、显示绑定(在某个对象上强制调用函数)

call在调用 foo 时强制把它的 this绑定到 obj

function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); //

硬绑定

function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); //
bar.call(window,3) // 2 3(硬绑定的 bar 不可能再修改它的 this)

辅助函数

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 ); //

Function.prototype.bind ES5 内置的硬绑定函数

function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); //

4、new绑定

(1)创建一个全新的对象。

(2) 这个新对象会被执行[[Prototype]]连接。

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

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

function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); //

简单实现一个new函数

function objectFactory() {

      var obj = new Object(null),

      Constructor = [].shift.call(arguments);

      obj.__proto__ = Constructor.prototype;

      var ret = Constructor.apply(obj, arguments);

      return typeof ret === 'object' ? ret : obj;
};

this 绑定的优先级

function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); //
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); //

obj1.foo.call( obj2 );输出3,说明绑定的是obj2,显式绑定>隐试绑定

function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); //

console.log( obj1.a ); // 2 说明new obj1.foo( 4 ) 没有先操作隐试绑定,new 绑定 > 隐式绑定

function bind(fn, obj) {
return function() {
fn.apply( obj, arguments );
};
}
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = bind(foo,obj1 );
bar( 2 );
console.log( obj1.a ); //
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // undefined

new 绑定不能修改显式绑定后函数的this绑定

但是使用ES内置bind的属性,console.log( baz.a ); 输出3,说明new 绑定成功,new 绑定 >显式绑定

function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); //
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); //

实现 ES5中的bind

if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
// bind 关在函数的原型链上
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, // 将当前this保存起来
fNOP = function () { },
fBound = function () {
// 当前对象是否在fNOP原型链上,是将this指向当前对象,不是将this指向oThis
return fToBind.apply((this instanceof fNOP && oThis ? this : oThis),
aArgs.concat(Array.prototype.slice.call(arguments)))
}
fNOP.prototype = this.prototype; // 继承this的原型
fBound.prototype = new fNOP(); // 将fBound指向fNOP的实例
return fBound;
};
}

因为return fToBind.apply((this instanceof fNOP && oThis ? this : oThis),这段代码判断了如果new 一个bind硬绑定的函数,将新生成的对象优先绑定到this

软绑定实现,保留隐试绑定和显示绑定

if (!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this;
var curried = [].slice.call(arguments, 1);
var bound = function () {
return fn.apply(
(!this || this === (window || global)) ?
obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
}

!this || this === (window || global)) ? obj : this, 在绑定对象为全局或者找不到时,采用显示绑定,然则采用隐试绑定。

更安全的this

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null ); // 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

this 绑定到空对象减少副作用,如果传入nullthis默认会绑定全局对象,这样会修改全局对象

ES6中的箭头函数

function foo() {
setTimeout(() => {
// 这里的 this 在词法上继承自 foo()
console.log( this.a );
},100);
}
var obj = {
a:2
};
foo.call( obj ); //

箭头函数的内部this继承自其外部函数的this作用域。

总结

this 词法主要用于绑定函数调用时的执行上下文,主要有默认绑定,隐试绑定,显示绑定,new绑定四种规则,其中运用显示绑定可以实现硬绑定,而ES5为函数对象内置了Function.prototype.bind 属性。编写代码时,尽量编写统一规范的风格:

  1. 只使用词法作用域并完全抛弃错误this风格的代码;

  2. 完全只采用 this 风格,在必要时使用 bind(..),尽量避免使用 self = this 和箭头函数。

最新文章

  1. 【Java EE 学习 67 下】【OA项目练习】【SSH整合JBPM工作流】【JBPM项目实战】
  2. linux rpm问题:怎样查看rpm安装包的安装路径
  3. Java反编译利器-Jad, Jode, Java Decompiler等及其IDE插件
  4. linux套件安装过程中configure,make,make install的作用
  5. PAT-乙级-1020. 月饼 (25)
  6. JSP中解决获取请求参数中文乱码问题
  7. 多选select实现左右添加删除
  8. HTML DOM(一):认识DOM
  9. Appium移动自动化测试(三)--安装Android模拟器(转)
  10. 浙江大学2015年校赛F题 ZOJ 3865 Superbot BFS 搜索
  11. Apache Curator获得真正的
  12. NYOJ116 士兵杀敌(二)
  13. SpringBoot整合SpringSecurity,SESSION 并发管理,同账号只允许登录一次
  14. Centos7 update dotnet 无法识别
  15. Linux下如何查看进程准确启动时间
  16. Java使用Future设置方法超时
  17. scrapy爬虫框架处理流程简介
  18. Spring基于纯注解方式的使用
  19. Git常用命令速记与入门
  20. C/C++ 多继承{虚基类,虚继承,构造顺序,析构顺序}

热门文章

  1. java的异常体系 及强制转换
  2. Java高级项目实战03:CRM系统数据库设计
  3. SpringCloud学习之—Eureka集群搭建
  4. SpringCloud之Ribbon负载均衡的入门操作
  5. 07.JS对象-2
  6. MySql数据库精简与绿色启动
  7. Python爬虫实战教程:爬取网易新闻;爬虫精选 高手技巧
  8. appium+python+unittest+HTMLRunner登录自动化测试报告
  9. 【python基础语法】国庆扩展练习题
  10. 常用 SQL Server 脚本