JavaScript は非常に柔軟な言語だと言われていますが、実際にはわかりにくい言語とも言えます。関数型プログラミングとオブジェクト指向プログラミングを組み合わせ、動的言語機能と組み合わせることで、非常に強力です (実際、C と比較することはできません ^_^)。
ここでのトピックはこれです。あまり深入りしないようにしましょう。これ自体は元々非常に単純で、常にクラスの現在のインスタンスを指しており、値を割り当てることはできません。この前提は、これがクラス/オブジェクトから分離できないということです。これは、これがオブジェクト指向言語の一般的なキーワードであることを意味します。極端に言えば、オブジェクト指向スタイルではなく関数型スタイルを使用して JS を作成すると、すべてのコードでこれが大幅に少なくなるか、まったくなくなることさえあります。これを使用するときは、オブジェクト/クラスのアプローチを使用して開発する必要があることに留意してください。そうでない場合、これは関数呼び出しの単なる副作用です。
これは JS です
関数内で作成
(一口) 呼び出されたときに関数にバインドされたオブジェクトを指します
これは割り当てることはできませんが、呼び出し/適用によって変更できます
以前これを使ったときは、誰を指しているのかわからず不安になることがよくありました。 これが使用されているすべての場所のリストです
this とコンストラクター
これとオブジェクト
これと関数
地球環境のこれ
これと DOM/イベント
これは呼び出し/適用によって変更できます
ES5 の新しいバインドと this
ES6のアロー機能とこれ
1. これとコンストラクター
これ自体はクラス定義時にコンストラクタ内で使用する必要があるものなので、コンストラクタと一緒になるのが自然です。
/** * 页签 * * @class Tab * @param nav {string} 页签标题的class * @param content {string} 页面内容的class * */ function Tab(nav, content) { this.nav = nav this.content = content } Tab.prototype.getNav = function() { return this.nav; }; Tab.prototype.setNav = function(nav) { this.nav = nav; }; Tab.prototype.add = function() { };
JavaScript の規約に従って、これは属性/フィールドに付加され、メソッドはプロトタイプに配置される必要があります。
2. これとオブジェクト
JS のオブジェクトはクラスなしで作成できます。クラスがオブジェクトのテンプレートであり、オブジェクトはテンプレートからコピーされることに驚く人もいるでしょう。クラスなしでオブジェクトを作成するにはどうすればよいでしょうか。 JS ではそれが可能で、クラスを 1 つも作成せずに、数万行の関数コードを作成できます。ちなみに、OOP はクラス指向プログラミングではなく、オブジェクト指向プログラミングについて話しますよね^_^。
var tab = { nav: '', content: '', getNav: function() { return this.nav; }, setNav: function(n) { this.nav = n; } }
3. this と関数
まず、これと独立した関数を一緒にするのは意味がありません。前述したように、これはオブジェクト指向に関連するはずです。純粋な関数は、単なる低レベルの抽象化、カプセル化、再利用です。以下の通り
function showMsg() { alert(this.message) } showMsg() // undefined
showMsg を定義し、this.message が未定義の関数として呼び出します。したがって、純粋な関数でこれを使用することは厳禁ですが、このように書かれていて、呼び出しメソッドが call/apply
である場合があります。function showMsg() { alert(this.message) } var m1 = { message: '输入的电话号码不正确' } var m2 = { message: '输入的身份证号不正确' } showMsg.call(m1) // '输入的电话号码不正确' showMsg.call(m2) // '输入的身份证号不正确'
この方法で一部のコードを保存できます。たとえば、2 つのクラス/オブジェクトに同様のメソッドがある場合、1 つを定義してそれぞれのプロトタイプとオブジェクトにバインドするだけで済みます。 。現時点では、実際にはまだオブジェクトまたはクラス (メソッド 1/2) を間接的に使用しています。
4.地球環境のこれ
前述したように、これは「関数が呼び出されたときに、その関数にバインドされたオブジェクトを指す」というもので、この文は不自然ですが、まったく正しいです。グローバル環境には、ブラウザ環境ではウィンドウ、ノード環境ではグローバルという異なるホスト オブジェクトがあります。ここではブラウザ環境に焦点を当てます。
ブラウザ環境では、非関数内の this は window を指します
alert(window=== this) // true
したがって、多くのオープンソース JS ライブラリが次のように記述されているのがわかります
(関数() {
// ...
})(これ);
または、次のように書きます
(関数() {
// ...
}).call(this);
たとえば、アンダースコアや requirejs の一般的な考え方は、グローバル変数ウィンドウを匿名関数に渡し、それをキャッシュして直接アクセスを避けることです。なぜキャッシュが必要かというと、これは JS スコープ チェーンと関係があり、外部識別子の読み取りパフォーマンスが低下します。関連する知識はご自身で確認してください。そうしないと、非常識すぎる可能性があります。
ブラウザーでは、関数以外の中で var を使用して直接宣言された変数はデフォルトでグローバル変数になり、デフォルトで属性としてウィンドウに表示されます。
var andy = '刘德华' alert(andy === window.andy) // true alert(andy === this.andy) // true alert(window.andy === this.andy) // true
この機能により、
などの一部の筆記試験の問題が発生します。var x = 10; function func() { alert(this.x) } var obj = { x: 20, fn: function() { alert(this.x) } } var fn = obj.fn func() // 10 fn() // 10
没错,最终输出的都是全局的 10。永远记住这一点:判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window。
5. this 和 DOM/事件
W3C 把 DOM 实现成了各种节点,节点嵌套一起形成 DOM tree。节点有不同类型,如文本节点,元素节点等10多种。元素节点又分成了很多,对写HTML的人来说便是很熟悉的标签(Tag),如 div,ul,label 等。 看 W3C 的 API 文档,会发现它完全是按照面向对象方式实现的各种 API,有 interface,extends 等。如
看到了吧,这是用 Java 写的,既然是用面向对象方式实现的API,一定有类/对象(废话^_^),有 类/对象,则一定有 this (别忘了这篇文章的中心主题)。所有的 HTML tag 类命名如 HTMLXXXElement,如
HTMLDivElement
HTMLLabelElement
HTMLInputElement
...
前面说过 this 是指向当前类的实例对象,对于这些 tag 类来说,不看其源码也知它们的很多方法内部用到的 this 是指向自己的。 有了这个结论,写HTML和JS时, this 就清晰了很多。
示例A
<!-- this 指向 div --> <div onclick="alert(this)"></div>
示例B
<div id="nav"></div> <script> nav.onclick = function() { alert(this) // 指向div#nav } </script>
示例C
$('#nav').on('click', function() { alert(this) // 指向 nav })
以上三个示例可以看到,在给元素节点添加事件的时候,其响应函数(handler)执行时的 this 都指向 Element 节点自身。jQuery 也保持了和标准一致,但却让人迷惑,按 “this 指向调用时所在函数所绑定的对象” 这个定义,jQuery 事件 handler 里的 this,应该指向 jQuery 对象,而非 DOM 节点。因此你会发现在用 jQuery 时,经常需要把事件 handler 里的 element 在用 $ 包裹下变成 jQuery 对象后再去操作。比如
$('#nav').on('click', function() { var $el = $(this) // 再次转为 jQuery 对象,如果 this 直接为 jQuery 对象更好 $el.attr('data-x', x) $el.attr('data-x', x) })
有人可能有如下的疑问
<div id="nav" onclick="getId()">ddd</div> <script> function getId() { alert(this.id) } </script>
点击 div 后,为什么 id 是 undefined,不说是指向的 当前元素 div 吗? 如果记住了前面提到的一句话,就很清楚为啥是 undefined,把这句话再贴出来。
判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window
这里函数 getId 调用时没有绑定在任何对象上,可以理解成这种结构
div.onclick = function() { getId() }
getId 所处匿名函数里的 this 是 div,但 getId 自身内的 this 则不是了。 当然 ES5 严格模式下还是有个坑。
6. this 可以被 call/apply 改变
call/apply 是函数调用的另外两种方式,两者的第一个参数都可以改变函数的上下文 this。call/apply 是 JS 里动态语言特性的表征。动态语言通俗的定义
程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除,即程序在运行时可以发生结构上的变化
通常有以下几点特征表示它为动态语言
动态的数据类型
动态的函数执行
动态的方法重写
动态语言多从世界第二门语言 LISP 发展而来,如死去的 SmallTalk/VB,目前还活着的 Perl/Python, 以及还流行的 Ruby/JavaScript。JS 里动态数据类型的体现便是弱类型,执行的时候才去分析标识符的类型。函数动态执行体现为 eval,call/aply。方法重写则体现在原型重写。不扯远,这里重点说下 call/apply 对 this 的影响。
var m1 = { message: 'This is A' } var m2 = { message: 'This is B' } function showMsg() { alert(this.message) } showMsg() // undefined showMsg.call(m1) // 'This is A' showMsg.call(m2) // 'This is B'
可以看到单独调用 showMsg 返回的是 undefined,只有将它绑定到具有 message 属性的对象上执行时才有意义。发挥想象力延伸下,如果把一些通用函数写好,可以任意绑定在多个类的原型上,这样动态的给类添加了一些方法,还节省了代码。这是一种强大的功能,也是动态语言的强表现力的体现。
经常会听到转向 Ruby 或 Python 的人提到“编程的乐趣”,这种乐趣是源自动态语言更接近人的思维(而不是机器思维),更符合业务流程而不是项目实现流程。同样一个功能,动态语言可以用更小的代码量来实现。动态语言对程序员生产力的提高,是其大行其道的主要原因。
性能方面,动态语言没有太大的优势,但动态语言的理念是:优化人的时间而不是机器的时间。提高开发者的生产力,宁肯牺牲部分的程序性能或者购买更高配置的硬件。随着IT业的不断发展和摩尔定律的作用,硬件相对于人件一直在贬值,这个理念便有了合理的现实基础。
JS 里的 call/apply 在任何一个流行的 lib 里都会用到,但几乎就是两个作用
配合写类工具实现OOP,如 mootools, ClassJS, class.js,
修复DOM事件里的 this,如 jQuery, events.js
关于 call 和 apply 复用:利用apply和arguments复用方法
关于 call 和 apply 的性能问题参考: 冗余换性能-从Backbone的triggerEvents说开了去
7. ES5 中新增的 bind 和 this
上面 6 里提到 call/apply 在 JS 里体现动态语言特性及动态语言的流行原因,其在 JS 用途如此广泛。ES5发布时将其采纳,提了一个更高级的方法 bind。
var modal = { message: 'This is A' } function showMsg() { alert(this.message) } var otherShowMsg = showMsg.bind(modal) otherShowMsg() // 'This is A'
因为是ES5才加的,低版本的IE不支持,可以修复下Function.prototype。bind 只是 call/apply 的高级版,其它没什么特殊的。
8. ES6 箭头函数(arrow function) 和 this
ES6 在今年的 6月18日 正式发布(恰京东店庆日同一天,^_^),它带来的另一种类型的函数 - 箭头函数。箭头函数的一个重要特征就是颠覆了上面的一句话,再贴一次
判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window
是的,前面一直用这句话来判断 this 的指向,在箭头函数里前面半句就失效了。箭头函数的特征就是,定义在哪,this 就指向那。即箭头函数定义在一个对象里,那箭头函数里的 this 就指向该对象。如下
var book = { author: 'John Resig', init: function() { document.onclick = ev => { alert(this.author) ; // 这里的 this 不是 document 了 } } }; book.init()
对象 book 里有一个属性 author, 有一个 init 方法, 给 document 添加了一个点击事件,如果是传统的函数,我们知道 this 指向应该是 document,但箭头函数会指向当前对象 book。
箭头函数让 JS 回归自然和简单,函数定义在哪它 this 就指向哪,定义在对象里它指向该对象,定义在类的原型上,指向该类的实例,这样更容易理解。
总结:
函数的上下文 this 是 JS 里不太好理解的,在于 JS 函数自身有多种用途。目的是实现各种语言范型(面向对象,函数式,动态)。this 本质是和面向对象联系的,和写类,对象关联一起的, 和“函数式”没有关系的。如果你采用过程式函数式开发,完全不会用到一个 this。 但在浏览器端开发时却无可避免的会用到 this,这是因为浏览器对象模型(DOM)本身采用面向对象方式开发,Tag 实现为一个个的类,类的方法自然会引用类的其它方法,引用方式必然是用 this。当你给DOM对象添加事件时,回调函数里引用该对象就只能用 this 了。
明白了么?
相信看完全文以后,this不再是坑~