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 和箭头函数。

【你不知道的javaScript 上卷 笔记5】javaScript中的this词法的更多相关文章

  1. 你不知道的JavaScript上卷笔记

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章   初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...

  2. 【你不知道的javaScript 上卷 笔记3】javaScript中的声明提升表现

    console.log( a ); var a = 2; 执行输出undefined a = 2; var a; console.log( a ); 执行输出2 说明:javaScript 运行时在编 ...

  3. Javascript学习笔记1 javascript的特点

    ..对于网页而言,Javascript无处不在,对于英语不好的人它简直是噩梦般的存在,但形式所逼,今天开始着手学习!希望自己能坚持下去.从什么地方着手,我的目标是从大处着眼,从应用着眼,不抠细节,反正 ...

  4. JavaScript学习笔记(4)——JavaScript语法之变量

    一.变量可以使用短名称(比如 x 和 y),也可以使用描述性更好的名称(比如 age, sum, totalvolume). 变量必须以字母开头 变量也能以 $ 和 _ 符号开头(不过我们不推荐这么做 ...

  5. 1.2(JavaScript学习笔记)JavaScript HTML DOM

    一.DOM DOM全称为document object model(文档对象模型). 此处的文档指当前HTML文档,对象指HTML标签. 当网页被加载时,浏览器会创建页面的文档对象模型. 下面结合具体 ...

  6. Javascript学习笔记3 Javascript与BOM简介

    什么是BOM BOM是browser object model的缩写,简称浏览器对象模型 BOM提供了独立于内容而与浏览器窗口进行交互的对象 由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象 ...

  7. 【你不知道的javaScript 上卷 笔记7】javaScript中对象的[[Prototype]]机制

    [[Prototype]]机制 [[Prototype]]是对象内部的隐试属性,指向一个内部的链接,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototyp ...

  8. 【你不知道的javaScript 上卷 笔记6】javaScript中的对象相关内容

    一.创建一个对象的语法 var myObj = { key: value // ... };//字面量 var myObj = new Object(); //new myObj.key = valu ...

  9. 【你不知道的javaScript 上卷 笔记4】javaScript 中闭包的一些运用

    什么是闭包 闭包是javaScript语言的一种特性,在 javaScript 中以函数作为承接单元.当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行. fun ...

随机推荐

  1. C#设计模式学习笔记:(19)策略模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8057654.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第七个模式--策 ...

  2. sklearn使用小贴士

    1 sklearn简介 Scikit-learn(sklearn)是机器学习中的第三方模块,封装了常用的机器学习算法,涉及回归.降维.分类以及聚类等,提供python接口. 虽然sklearn容纳的算 ...

  3. html文件如何下载文件

    HTML中下载文件两种方法:a标签下载:js下载; <!-- 网页地址和图片地址同源的情况下这样是可以的,不同源则是直接打开这个地址 --> <!-- 方法一:同源情况下载 --&g ...

  4. 什么是AOP面向切面编程思想

    一.什么是AOP? 1.AOP不是一种语言,是一种编程范式 常见的编程范式: 面向过程.面向对象.函数式编程.事件驱动编程等 2.AOP可以解决特定问题,不能解决所有问题. 3.是面向对象的补充,不是 ...

  5. 使用TensorRT对人脸检测网络MTCNN进行加速

    前言 最近在做人脸比对的工作,需要用到人脸关键点检测的算法,比较成熟和通用的一种算法是 MTCNN,可以同时进行人脸框选和关键点检测,对于每张脸输出 5 个关键点,可以用来进行人脸对齐. 问题 刚开始 ...

  6. Git操作:一次性强制push所有分支

    现在手上有两个分支,master和rotation,想一次性推送所有分支,可以用--all参数来实现: git push --all origin 如果远程仓库有更改,但你需要直接推送,那就可以使用强 ...

  7. vue中报错Do not use built-in or reserved HTML elements as component id details

    原因是定义了一个叫做details的comonent 跟现有的html网页中的标签重合 export default { name: 'details', data () { return { equ ...

  8. opencv —— Sobel 一阶导数算子、Scharr 滤波器 一阶导数用于边缘检测

    sobel 算子的基本概念 sobel 算子是一个主要用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导,用于计算图像灰度函数的近似梯度. 其基础来自于一个事实,即在边缘部分,像素值出现“跳跃”或 ...

  9. node模块化开发基本知识学习笔记

    传统非模块化开发缺点: 1.命名冲突 2.文件依赖 标准的模块化规范: 1.AMD-requirejs 2.CMD-seajs 服务器端模块化规范: 1.CommonJS-Node.js 模块化相关的 ...

  10. Essential C++ 笔记-1

    本文作者为C++初学者,学习之中难免有误,该文章仅为参考 面向对象概述 继承:改变类之间的关系 多态:让基类的pointer或refence得以十分透明的指向基类的某个派生对象 继承 继承发生在对象与 ...