你不知道的JS之 this 和对象原型(一)this 是什么
原文:你不知道的js系列
JavaScript 的 this 机制并没有那么复杂
为什么会有 this?
在如何使用 this 之前,我们要搞清楚一个问题,为什么要使用 this。
下面的代码尝试去说明 this 的使用动机:
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello, I'm " + identify.call( this );
console.log( greeting );
}
var me = {
name: "Kyle"
};
var you = {
name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER
这段代码使得函数 identify() 和 speak() 可以在多个上下文(me 和 you)对象中重用,不用给每个对象分别创建函数。
如果不用 this,你也可以将上下文对象直接传入函数:
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify( context );
console.log( greeting );
}
identify( you ); // READER
speak( me ); // Hello, I'm KYLE
然而 this 机制可以隐式地传递一个对象引用,使得 API 设计得更简洁和更容易复用。
你的使用模式越复杂,你就能更加明白,显式传递一个参数经常比传递 this 上下文还混乱。
困惑
在解释 this 如何工作之前,必须要先摒弃错误的概念。开发者们总是太过依赖 this 的字面意思。
引用自身 Itself
一种普遍的错误是认为 this 指代这个函数自身。
为什么你会想从一个函数内部引用它自己呢,通常的原因是递归,或者事件回调函数在被调用之后解除绑定。
JS 新手会认为将函数作为对象引用可以在函数调用期间存储状态(属性的值)。这确实是可以的但是用处有限,后面会介绍其它模式,除了函数对象本身还有更好的存储状态的地方。
下面的代码会说明,this并不会像我们以为的那样让函数得到对自身的引用:
function foo(num) {
console.log( "foo: " + num );
// keep track of how many times `foo` is called
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 0 -- WTF?
foo.count 还是 0 ,循环确实执行了 4 次,console.log 也确实被调用了 4 次。
foo.count = 0 执行之后,实际上给函数对象 foo 添加了一个属性 count。
但是在函数内部的 this.count 中,this 实际上并不指向这个函数对象,即使这个属性名字是一样的,但属性所在的对象是不同的。
如果 foo 的属性 count 的值没有改变,那么我们改变的究竟是什么。实际上,如果你再深究一下,就会发现,这段代码意外地创建了一个全局变量 count,而且当时会有一个值 NaN(具体看这个系列的第二节)。
很多开发者就会通过别的方式避免这个问题,比如创建另外一个对象储存这个属性 count:
function foo(num) {
console.log( "foo: " + num );
// keep track of how many times `foo` is called
data.count++;
}
var data = {
count: 0
};
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( data.count ); //
这确实解决了问题,但是很遗憾这忽略了真正的问题——不理解 this 的含义和用法,只是回到熟悉的词法作用域机制。
如果想在一个函数对象内部引用自身,this 是不够的,你需要一个标识符:
function foo() {
foo.count = 4; // `foo` refers to itself
}
setTimeout( function(){
// anonymous function (no name), cannot
// refer to itself
}, 10 );
在第一个函数中,函数被命名为 foo,这个标识符 foo 就可以用来指代这个函数对象自身。
但在第二段中,回调函数没有名字,所以没办法引用自己。
注:老派的已经被废弃的 arguments.callee 在函数中可以用来指代正在执行的函数对象。这是在匿名函数内部访问函数对象的唯一方式。
当然最好的方式还是避免匿名函数的使用。
另外一种解决办法就是使用 foo 标识符,不使用 this:
function foo(num) {
console.log( "foo: " + num );
// keep track of how many times `foo` is called
foo.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); //
然而这种方法同样回避了对 this 的理解。
另外一种解决这个问题的方式是,将 this 强制绑定到 foo 这个函数对象上:
function foo(num) {
console.log( "foo: " + num );
// keep track of how many times `foo` is called
// Note: `this` IS actually `foo` now, based on
// how `foo` is called (see below)
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
// using `call(..)`, we ensure the `this`
// points at the function object (`foo`) itself
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); //
作用域的引用 Its Scope
第二个常见的关于 this 的错误理解是,this 指向这个函数的作用域。这是一个有点狡猾的问题,因为在某种意义上这种说法是有些正确的,但在另一种意义上,这又是被误导的。
首先,this 并没有指向函数的词法作用域。作用域确实就像是一个包含所有标识符属性的对象,但是这个作用域 “对象” 是无法被代码直接访问的,这是引擎内部实现的。
所以下面的代码是错误的:
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); //undefined
你可能觉得这段代码很做作,但这是摘自一些帮助论坛里的真实代码。
首先,这段代码试图通过 this.bar() 引用函数 bar(),能运行起来也是巧合。调用 bar() 最自然的方式就是直接使用标识符引用,去掉前面的 this。
然而,写这段代码的开发者其实是想让 bar() 访问 foo() 内部的变量 a,但 this 不能被用来查询词法作用域的。
this 到底是什么
前面讲到过,this 是在运行时绑定的,它的上下文环境取决于函数调用的条件。this 的绑定和函数声明的位置没有关系,和函数调用的位置有关。
当一个函数被调用时,一个执行上下文被创建。这个上下文记录包含函数调用的位置,函数调用的方式以及传入的参数这些信息。this 的引用就是在这个时候决定的。
在下一节中,会介绍根据一个函数的调用位置确定它执行过程中将如何绑定this。
小结:
- this 既不指代函数本身,也不指代函数的词法作用域。
- this 是在函数调用的时候绑定的,它引用的内容完全取决于函数调用的位置。
你不知道的JS之 this 和对象原型(一)this 是什么的更多相关文章
- 关于js的对象原型继承(一)
javascript中,对象的继承是通过原型去继承. 可以这样理解:js中的对象,包含的除了属性和方法,还有一个最基本的原型__proto__对象.这个原型__proto__指向谁,这个对象就继承谁. ...
- jquery实现点击展开列表同时隐藏其他列表 js 对象操作 对象原型操作 把一个对象A赋值给另一个对象B 并且对象B 修改 不会影响 A对象
这篇文章主要介绍了jquery实现点击展开列表同时隐藏其他列表的方法,涉及jquery鼠标事件及节点的遍历与属性操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下 本文实例讲述了jquery实现点击 ...
- JS的对象原型
1.对象 1.1 语法 对象可以通过两种形式定义:声明(文字)形式和构造形式. 对象的文字语法: var myObj = { key : value //... }; 对象的构造语法: var myO ...
- 关于JS对象原型prototype与继承,ES6的class和extends · kesheng's personal blog
传统方式:通过function关键字来定义一个对象类型 1234567891011 function People(name) { this.name = name}People.prototype. ...
- 读书笔记-你不知道的JS上-混入与原型
继承 mixin混合继承 function mixin(obj1, obj2) { for (var key in obj2) { //重复不复制 if (!(key in obj1)) { obj1 ...
- 深度剖析前端JavaScript中的原型(JS的对象原型)
这张图片有点劝退了,哈哈哈~ 通过原型机制,JavaScript 中的对象从其他对象继承功能特性:这种继承机制与经典的面向对象编程语言的继承机制不同.本文将探讨这些差别,解释原型链如 ...
- 你不知道的JS之作用域和闭包 附录
原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...
- JS基础-该如何理解原型、原型链?
JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...
- js之oop <二> 对象属性
js中对象属性可以动态添加和删除.删除对象属性用delete关键字. function obj(){ } var oo = new obj(); oo.a = "a"; oo.b ...
随机推荐
- hibernate核心类及常用方法
Configuration configure = new Configuration().configure(); SessionFactory factory = configure.buildS ...
- kubenetes_V1.14.0 安装部署
k8s的安装有多种方式,如yum安装,kubeadm安装,kubemini安装,二进制安装(生产环境多采用此方式精确控制安装)等.本文是入门系列验证,之前进行过yum安装,可以查看文章<k8s入 ...
- C#中上下文Context的理解
上下文指的是 进程间占有的资源空间. 当一个进程时间片到了或者资缺的时候就会让出cpu 当另一个进程开始始用CPU之前,系统要保存即将退出进程的执行状态,以便轮到时间片或有资源的时候现场恢复.这就所谓 ...
- Elasticsearch一些使用笔记(持续更新)
这篇博客记录这一些运维ES的一些经验. 1.节点磁盘使用率过高,导致ES集群shard无法分配,丢失数据? 有两个配置,分配副本的时候 参数名称 默认值 含义 cluster.routing.allo ...
- W3CSchool闯关笔记(Bootstrap)
该闯关内容与JS闯关衔接. 每一题的答案均在注释处, 第一关:把所有的HTML内容放在一个包含有container-fluid的class名称的div下(注意,是所有的HTML内容,style标签属于 ...
- Font Awesome,一套绝佳的图标字体库和CSS框架
http://fontawesome.dashgame.com/ http://www.runoob.com/font-awesome/fontawesome-tutorial.html Font A ...
- Django与Ajax
一.Ajax简介 AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”.即使用Javascript语言与服务器进行异步交互,传输 ...
- maven 导包报错
作为初学者本应当是持之以恒的但是很长时间没有冒泡了这次冒个泡写maven项目的时候遇到了很多的bug,今天给大家分享一下解决的办法(常见的错误就是导不进来自己想要的包)要么就是导包报错以下是解决方法 ...
- webpack的在开发生产时的具体功能
webpack的在开发生产时的具体功能 开发时需要调试代码,在打包过后如果出错我们就需要调试工具来帮我们改正错误.Source Map就是帮我们解决这个难题的.他要在我们的webpack.config ...
- [Linux][转载]Curl命令详解
命令:curl 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,是一款很强大的http命令行工具,当处在无界面的服务器上的时候,利用curl下载上传文件是较为方便的事情. 语法 ...