走在前端的大道上javascript
本篇将本身读过的相关 this指向 的文章中,对本身有启发的章节片断总结在这(会对原文进行删改),会不断丰富提炼总结更新。css
this的指向在函数定义的时候是肯定不了的,只有函数执行的时候才能肯定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为何会有问题,虽然网上大部分的文章都是这样说的,虽然在不少状况下那样去理解不会出什么问题,可是实际上那样理解是不许确的,因此在你理解this的时候会有种琢磨不透的感受) —— —— 完全理解js中this的指向,没必要硬背。
构造函数模式
的时候,this指向新生成的实例function Aaa(name){ this.name= name; this.getName=function(){ console.log(this.name) } } var a = new Aaa('kitty'); a.getName() // 'kitty' var b = new Aaa('bobo'); b.getName() // 'bobo'
若是 new 关键词出如今被调用函数的前面,那么JavaScript引擎会建立一个新的对象,被调用函数中的this指向的就是这个新建立的函数。html
function ConstructorExample() { console.log(this); this.value = 10; console.log(this); } new ConstructorExample(); // -> ConstructorExample {} // -> ConstructorExample { value: 10 }
构造函数版this:前端
function Fn(){ this.user = "追梦子"; } var a = new Fn(); console.log(a.user); //追梦子
这里之因此对象a能够点出函数Fn里面的user是由于new关键字能够改变this的指向,将这个this指向对象a,为何我说a是对象,由于用了new关键字就是建立一个对象实例,咱们这里用变量a建立了一个Fn的实例(至关于复制了一份Fn到对象a里面),此时仅仅只是建立,并无执行,而调用这个函数Fn的是对象a,那么this指向的天然是对象a,那么为何对象a中会有user,由于你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。vue
apply/call调用模式
的时候,this指向apply/call方法中的第一个参数var list1 = {name:'andy'} var list2 = {name:'peter'} function d(){ console.log(this.name) } d.call(list1) // 'andy' d.call(list2) // 'peter'
若是经过apply、call或者bind的方式触发函数,那么函数中的this指向传入函数的第一个参数。java
function fn() { console.log(this); } var obj = { value: 5 }; var boundFn = fn.bind(obj); boundFn(); // -> { value: 5 } fn.call(obj); // -> { value: 5 } fn.apply(obj); // -> { value: 5 }
在没有学以前,一般会有这些问题。jquery
var a = { user:"追梦子", fn:function(){ console.log(this.user); } } var b = a.fn; b(); //undefined
咱们是想打印对象a里面的user却打印出来undefined是怎么回事呢?若是咱们直接执行a.fn()是能够的。git
var a = { user:"追梦子", fn:function(){ console.log(this.user); } } a.fn(); //追梦子
虽然这种方法能够达到咱们的目的,可是有时候咱们不得不将这个对象保存到另外的一个变量中,那么就能够经过如下方法。github
var a = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } var b = a.fn; b.call(a);
经过在call方法,给第一个参数添加要把b添加到哪一个环境中,简单来讲,this就会指向那个对象。web
call方法除了第一个参数之外还能够添加多个参数,以下:
var a = { user:"追梦子", fn:function(e,ee){ console.log(this.user); //追梦子 console.log(e+ee); //3 } } var b = a.fn; b.call(a,1,2);
apply方法和call方法有些类似,它也能够改变this的指向
var a = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } var b = a.fn; b.apply(a);
一样apply也能够有多个参数,可是不一样的是,第二个参数必须是一个数组,以下:
var a = { user:"追梦子", fn:function(e,ee){ console.log(this.user); //追梦子 console.log(e+ee); //11 } } var b = a.fn; b.apply(a,[10,1]);
或者
var a = { user:"追梦子", fn:function(e,ee){ console.log(this.user); //追梦子 console.log(e+ee); //520 } } var b = a.fn; var arr = [500,20]; b.apply(a,arr);
//注意若是call和apply的第一个参数写的是null,那么this指向的是window对象
var a = { user:"追梦子", fn:function(){ console.log(this); //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…} } } var b = a.fn; b.apply(null);
bind方法和call、apply方法有些不一样,可是无论怎么说它们均可以用来改变this的指向。
先来讲说它们的不一样吧。
var a = { user:"追梦子", fn:function(){ console.log(this.user); } } var b = a.fn; b.bind(a);
咱们发现代码没有被打印,对,这就是bind和call、apply方法的不一样,实际上bind方法返回的是一个修改事后的函数。
var a = { user:"追梦子", fn:function(){ console.log(this.user); } } var b = a.fn; var c = b.bind(a); console.log(c); //function() { [native code] }
那么咱们如今执行一下函数c看看,能不能打印出对象a里面的user
var a = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } var b = a.fn; var c = b.bind(a); c();
ok,一样bind也能够有多个参数,而且参数能够执行的时候再次添加,可是要注意的是,参数是按照形参的顺序进行的。
var a = { user:"追梦子", fn:function(e,d,f){ console.log(this.user); //追梦子 console.log(e,d,f); //10 1 2 } } var b = a.fn; var c = b.bind(a,10); c(1,2);
总结:call和apply都是改变上下文中的this并当即执行这个函数,bind方法可让对应的函数想何时调就何时调用,而且能够将参数在执行的时候添加,这是它们的区别,根据本身的实际状况来选择使用。
方法调用模式
的时候,this指向方法所在的对象var a={}; a.name = 'hello'; a.getName = function(){ console.log(this.name) } a.getName() //'hello'
若是一个函数是某个对象的方法,而且对象使用句点符号触发函数,那么this指向的就是该函数做为那个对象的属性的对象,也就是,this指向句点左边的对象。
var obj = { value: 5, printThis: function() { console.log(this); } }; obj.printThis(); // -> { value: 5, printThis: ƒ }
由浅入深
function a(){ var user = "追梦子"; console.log(this.user); //undefined console.log(this); //Window } a();
按照咱们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就能够证实。
function a(){ var user = "追梦子"; console.log(this.user); //undefined console.log(this); //Window } window.a();
和上面代码同样吧,其实alert也是window的一个属性,也是window点出来的。
var o = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } o.fn();
这里的this指向的是对象o,由于你调用这个fn是经过o.fn()执行的,那天然指向就是对象o,这里再次强调一点,this的指向在函数建立的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,必定要搞清楚这个。
其实例子1和例子2说的并不够准确,下面这个例子就能够推翻上面的理论。
若是要完全的搞懂this必须看接下来的几个例子
var o = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } window.o.fn();
这段代码和上面的那段代码几乎是同样的,可是这里的this为何不是指向window,若是按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,咱们建立的变量其实是给window添加属性,因此这里能够用window点o对象。
这里先不解释为何上面的那段代码this为何没有指向window,咱们再来看一段代码。
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //12 } } } o.b.fn();
这里一样也是对象o点出来的,可是一样this并无执行它,那你确定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不许确,接下来我将补充一句话,我相信你就能够完全的理解this的指向的问题。
- 状况1:若是一个函数中有this,可是它没有被上一级的对象所调用,那么this指向的就是window,这里须要说明的是在js的严格版中this指向的不是window,可是咱们这里不探讨严格版的问题,你想了解能够自行上网查找。
- 状况2:若是一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
- 状况3:若是一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3能够证实,若是不相信,那么接下来咱们继续看几个例子。
var o = { a:10, b:{ // a:12, fn:function(){ console.log(this.a); //undefined } } } o.b.fn();
尽管对象b中没有属性a,这个this指向的也是对象b,由于this只会指向它的上一级对象,无论这个对象中有没有this要的东西。
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //window } } } var j = o.b.fn; j();
这里this指向的是window,是否是有些蒙了?实际上是由于你没有理解一句话,这句话一样相当重要。
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,可是在将fn赋值给变量j的时候并无执行因此最终指向的是window,这和例子3是不同的,例子3是直接执行了fn。
this讲来说去其实就是那么一回事,只不过在不一样的状况下指向的会有些不一样,上面的总结每一个地方都有些小错误,也不能说是错误,而是在不一样环境下状况就会有不一样,因此我也没有办法一次解释清楚,只能你慢慢地的去体会。
函数调用模式
的时候,this指向windowfunction aa(){ console.log(this) } aa() //window
若是一个函数做为FFI被调用,意味着这个函数不符合以上任意一种调用方式,this指向全局对象,在浏览器中,便是window。
function fn() { console.log(this); } // If called in browser: fn(); // -> Window {stop: ƒ, open: ƒ, alert: ƒ, ...}
注意,第4条规则和第3条很相似,不一样的是当函数没有做为方法被调用时,它将自动隐式编程全局对象的属性——window。也就是当咱们调用 fn(),能够理解为window.fn(),根据第三条规则,fn()函数中的this指向的就是window。
function fn() { console.log(this); } // In browser: console.log(fn === window.fn); // -> true
将规则应用于实践
看一个代码示例,并使用上面的规则判断this的指向。
var obj = { value: 'hi', printThis: function() { console.log(this); } }; var print = obj.printThis; obj.printThis(); // -> {value: "hi", printThis: ƒ} print(); // -> Window {stop: ƒ, open: ƒ, alert: ƒ, ...}
obj.prinThis() ,根据第三条规则this指向的就是obj。根据第四条规则print()是FFI,所以this指向window。
obj对象中printThis这一方法实际上是函数的地址的一个引用,当咱们将obj.printThis赋值给print时,print包含的也是函数的引用,和obj对象一点关系也没有。obj只是碰巧拥有一个指向这个函数的引用的属性。
当不适用obj对象触发函数时,这个函数就是FFI。
应用多项规则
当出现多个上述规则时,将优先级高的“获胜”,若是规则2和规则3同时存在,则规则2优先:
var obj1 = { value: 'hi', print: function() { console.log(this); }, }; var obj2 = { value: 17 }; obj1.print.call(obj2); // -> { value: 17 }
若是规则1和规则3同时被应用,则规则1优先:
var obj1 = { value: 'hi', print: function() { console.log(this); }, }; new obj1.print(); // -> print {}
当this碰到return时
function fn() { this.user = '追梦子'; return {}; } var a = new fn; console.log(a.user); //undefined
再看一个
function fn() { this.user = '追梦子'; return function(){}; } var a = new fn; console.log(a.user); //undefined
再来
function fn() { this.user = '追梦子'; return 1; } var a = new fn; console.log(a.user); //追梦子
function fn() { this.user = '追梦子'; return undefined; } var a = new fn; console.log(a.user); //追梦子
什么意思呢?
若是返回值是一个对象,那么this指向的就是那个返回的对象,若是返回值不是一个对象那么this仍是指向函数的实例。
function fn() { this.user = '追梦子'; return undefined; } var a = new fn; console.log(a); //fn {user: "追梦子"}
还有一点就是虽然null也是对象,可是在这里this仍是指向那个函数的实例,由于null比较特殊。
function fn() { this.user = '追梦子'; return null; } var a = new fn; console.log(a.user); //追梦子
有些库会将this的指向绑定更有用的对象上,好比jQuery库,在事件处理程序中,this的指向不是全局对象而被绑定到了元素对象上。所以,若是你发现一些不能用上述5项规则解释的状况,请阅读你所使用的库的官方文档,找到关于该库是如何改变this的指向的,一般经过 bind 方法改变this的指向。
参考文章:
1.完全理解js中this的指向,没必要硬背。
2.javascript中this指向的规则
3.The Complete Rules to 'this'
4.JavaScript中的this
每一个函数调用都有与之相关的做用域和上下文。首先须要澄清的问题是上下文和做用域是不一样的概念。不少人常常将这两个术语混淆。
做用域(scope) 是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,做用域决定了代码区块中变量和其余资源的可见性。而上下文(context)是用来指定代码某些特定部分中 this 的值。
从根本上说,做用域是基于函数(function-based)的,而上下文是基于对象(object-based)的。换句话说,做用域是和每次函数调用时变量的访问有关,而且每次调用都是独立的。上下文老是被调用函数中关键字 this 的值,是调用当前可执行代码的对象的引用。说的通俗一点就是:this 取值,是在函数真正被调用执行的时候肯定的,而不是在函数定义的时候肯定的。
不管是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指向全局对象。固然具体的全局对象和宿主环境有关。
在浏览器中, window
对象同时也是全局对象:
console.log(this === window); // true
NodeJS 中,则是 global 对象:
console.log(this); // global
因为其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它能够是全局对象、当前对象或者任意对象,这彻底取决于函数的调用方式。JavaScript 中函数的调用有如下几种方式:做为函数调用,做为对象方法调用,做为构造函数调用,和使用 apply 或 call 调用。下面咱们将按照调用方式的不一样,分别讨论 this 的含义
做为函数直接调用时,要注意 2 种状况:
非严格模式
在非严格模式下执行函数调用,此时 this 默认指向全局对象。
function f1(){ return this; } //在浏览器中: f1() === window; //在浏览器中,全局对象是window //在Node中: f1() === global;
严格模式 ‘use strict’
在严格模式下,this 将保持他进入执行上下文时的值,因此下面的 this 并不会指向全局对象,而是默认为 undefined 。
'use strict'; // 这里是严格模式 function test() { return this; }; test() === undefined; // true
在 JavaScript 中,函数也是对象,所以函数能够做为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,内部的 this 指向该对象。
var Obj = { prop: 37, getProp: function() { return this.prop; } }; console.log(Obj.getProp()); // 37
上面的例子中,当 Obj.getProp() 被调用时,方法内的 this 将指向 Obj 对象。值得注意的是,这种行为根本不受函数定义方式或定义位置的影响。在前面的例子中,咱们在定义对象 Obj 的同时,将成员 getProp 定义了一个匿名函数。可是,咱们也能够首先定义函数,而后再将其附加到 Obj.getProp 。因此,下面的代码和上面的例子是等价的:
var Obj = { prop: 37 }; function independent() { return this.prop; } Obj.getProp = independent; console.log(Obj.getProp()); // logs 37
JavaScript 很是灵活,如今咱们把对象的方法赋值给一个变量,而后直接调用这个函数变量又会发生什么呢?
var Obj = { prop: 37, getProp: function() { return this.prop; } }; var test = Obj.getProp console.log(test()); // undefined
能够看到,这时候 this 指向全局对象,这个例子 test 只是引用了 Obj.getProp 函数,也就是说这个函数并不做为 Obj 对象的方法调用,因此,它是被看成一个普通函数来直接调用。所以,this 指向全局对象。
一些坑
咱们来看看下面这个例子:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 结果是 0 ,不是37! },1000) } }; Obj.getProp();
正如你所见, setTimeout 中的 this 向了全局对象,这里不是把它看成函数的方法使用吗?这一点常常让不少初学者疑惑;这种问题是不少异步回调函数中也会广泛会碰到,一般有个土办法解决这个问题,好比,咱们能够利用 闭包 的特性来处理:
var Obj = { prop: 37, getProp: function() { var self = this; setTimeout(function() { console.log(self.prop) // 37 },1000) } }; Obj.getProp();
其实,setTimeout
和 setInterval
都只是在全局上下文中执行一个函数而已,即便是在严格模式下:
'use strict'; function foo() { console.log(this); // Window } setTimeout(foo, 1);
记住 setTimeout
和 setInterval
都只是在全局上下文中执行一个函数而已,所以 this
指向全局对象。 除非你实用箭头函数,Function.prototype.bind
方法等办法修复。至于解决方案会在后续的文章中继续讨论。
JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不一样,JavaScript 并无类(class)的概念,而是使用基于原型(prototype)的继承方式。做为又一项约定通用的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。
当一个函数用做构造函数时(使用 new 关键字),它的 this 被绑定到正在构造的新对象,也就是咱们常说的实例化出来的对象。
function Person(name) { this.name = name; } var p = new Person('愚人码头'); console.log(p.name); // "愚人码头"
几个陷阱
若是构造函数具备返回对象的 return 语句,则该返回对象将是 new 表达式的结果。
function Person(name) { this.name = name; return { title : "前端开发" }; } var p = new Person('愚人码头'); console.log(p.name); // undefined console.log(p.title); // "前端开发"
相应的,JavaScript 中的构造函数也很特殊,若是不使用 new 调用,则和普通函数同样, this 仍然执行全局:
function Person(name) { this.name = name; console.log(this); // Window } var p = Person('愚人码头');
在箭头函数中,this 与封闭词法上下文的 this 保持一致,也就是说由上下文肯定。
var obj = { x: 10, foo: function() { var fn = () => { return () => { return () => { console.log(this); //{x: 10, foo: ƒ} 即 obj console.log(this.x); //10 } } } fn()()(); } } obj.foo();
obj.foo 是一个匿名函数,不管如何, 这个函数中的 this 指向它被建立时的上下文(在上面的例子中,就是 obj 对象)。这一样适用于在其余函数中建立的箭头函数:这些箭头函数的this 被设置为外层执行上下文。
// 建立一个含有bar方法的obj对象,bar返回一个函数,这个函数返回它本身的this, // 这个返回的函数是以箭头函数建立的,因此它的this被永久绑定到了它外层函数的this。 // bar的值能够在调用中设置,它反过来又设置返回函数的值。 var obj = { bar: function() { var x = (() => this); return x; } }; // 做为obj对象的一个方法来调用bar,把它的this绑定到obj。 // x所指向的匿名函数赋值给fn。 var fn = obj.bar(); // 直接调用fn而不设置this,一般(即不使用箭头函数的状况)默认为全局对象,若在严格模式则为undefined console.log(fn() === obj); // true // 可是注意,若是你只是引用obj的方法,而没有调用它(this是在函数调用过程当中设置的) var fn2 = obj.bar; // 那么调用箭头函数后,this指向window,由于它从 bar 继承了this。 console.log(fn2()() == window); // true
在上面的例子中,一个赋值给了 obj.bar 的函数(称为匿名函数 A),返回了另外一个箭头函数(称为匿名函数 B)。所以,函数B的this被永久设置为 obj.bar(函数A)被调用时的 this 。当返回的函数(函数B)被调用时,它this始终是最初设置的。在上面的代码示例中,函数B的 this 被设置为函数A的 this ,即 obj,因此它仍然设置为 obj,即便以一般将 this 设置为 undefined 或全局对象(或者如前面示例中全局执行上下文中的任何其余方法)进行调用。
填坑
咱们回到上面 setTimeout 的坑:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 结果是 0 ,不是37! },1000) } }; Obj.getProp();
一般状况我,咱们在这里指望输出的结果是 37 ,用箭头函数解决这个问题至关简单:
var Obj = { prop: 37, getProp: function() { setTimeout(() => { console.log(this.prop) // 37 },1000) } }; Obj.getProp();
相同的概念在定义在原型链中的方法也是一致的。若是该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就好像该方法原本就存在于这个对象上。
var o = { f : function(){ return this.a + this.b; } }; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5
在这个例子中,对象 p 没有属于它本身的f属性,它的f属性继承自它的原型。可是这对于最终在 o 中找到 f 属性的查找过程来讲没有关系;查找过程首先从 p.f 的引用开始,因此函数中的 this 指向 p 。也就是说,由于f是做为p的方法调用的,因此它的this 指向了 p 。这是 JavaScript 的原型继承中的一个有趣的特性。
你也会看到下面这种形式的老代码,道理是同样的:
function Person(name) { this.name = name; } Person.prototype = { getName:function () { return this.name } }; var p = new Person('愚人码头'); console.log(p.getName()); // "愚人码头"
再次,相同的概念也适用时的函数做为一个 getter 或者 一个 setter 调用。用做 getter 或 setter 的函数都会把 this 绑定到正在设置或获取属性的对象。
function sum() { return this.a + this.b + this.c; } var o = { a: 1, b: 2, c: 3, get average() { return (this.a + this.b + this.c) / 3; } }; Object.defineProperty(o, 'sum', { get: sum, enumerable: true, configurable: true}); console.log(o.average, o.sum); // logs 2, 6
注:Object.defineProperty()
顾名思义,为对象定义属性,方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。是ES5的属性, 支持IE8以上。
Object.defineProperty(obj, prop, descriptor)
参数
在js中咱们能够经过下面这几种方法定义属性
// (1) define someOne property name someOne.name = 'cover'; //or use (2) someOne['name'] = 'cover'; // or use (3) defineProperty Object.defineProperty(someOne, 'name', { value : 'cover' })
属性的状态设置
其中descriptor
的参数值得咱们关注下,该属性可设置的值有:
【value】 属性的值,默认为 undefined。
【writable】 该属性是否可写,若是设置成 false,则任何对该属性改写的操做都无效(但不会报错),对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
var someOne = { }; Object.defineProperty(someOne, "name", { value:"coverguo" , //因为设定了writable属性为false 致使这个量不能够修改 writable: false }); console.log(someOne.name); // 输出 coverguo someOne.name = "linkzhu"; console.log(someOne.name); // 输出coverguo
【configurable]】若是为false,则任未尝试删除目标属性或修改属性如下特性(writable, configurable, enumerable)的行为将被无效化,对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
var someOne = { }; Object.defineProperty(someOne, "name", { value:"coverguo" , configurable: false }); delete someOne.name; console.log(someOne.name);// 输出 coverguo someOne.name = "linkzhu"; console.log(someOne.name); // 输出coverguo
【enumerable】 是否能在for-in循环中遍历出来或在Object.keys中列举出来。对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
注意 在调用Object.defineProperty()方法时,若是不指定, configurable, enumerable, writable特性的默认值都是false,这跟以前所 说的对于像前面例子中直接在对象上定义的属性,这个特性默认值为为 true
并不冲突,以下代码所示:
//调用Object.defineProperty()方法时,若是不指定 var someOne = { }; someOne.name = 'coverguo'; console.log(Object.getOwnPropertyDescriptor(someOne, 'name')); //输出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true} //直接在对象上定义的属性,这个特性默认值为为 true var otherOne = {}; Object.defineProperty(otherOne, "name", { value:"coverguo" }); console.log(Object.getOwnPropertyDescriptor(otherOne, 'name')); //输出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}
【get】一旦目标对象访问该属性,就会调用这个方法,并返回结果。默认为 undefined。
【set】 一旦目标对象设置该属性,就会调用这个方法。默认为 undefined。
从上面,能够得知,咱们能够经过使用Object.defineProperty,来定义和控制一些特殊的属性,如属性是否可读,属性是否可枚举,甚至修改属性的修改器(setter)和获取器(getter)
那什么场景和地方适合使用到特殊的属性呢?
从上面,能够得知,咱们能够经过使用Object.defineProperty,来定义和控制一些特殊的属性,如属性是否可读,属性是否可枚举,甚至修改属性的修改器(setter)和获取器(getter)
实际运用
在一些框架,如vue、express、qjs等,常常会看到对Object.defineProperty的使用。那这些框架是如何使用呢?
MVVM中数据‘双向绑定’实现
待补充
优化对象获取和修改属性方式
这个优化对象获取和修改属性方式,是什么意思呢? 过去咱们在设置dom节点transform时是这样的。
//加入有一个目标节点, 咱们想设置其位移时是这样的 var targetDom = document.getElementById('target'); var transformText = 'translateX(' + 10 + 'px)'; targetDom.style.webkitTransform = transformText; targetDom.style.transform = transformText;
经过上面,能够看到若是页面是须要许多动画时,咱们这样编写transform属性是十分蛋疼的。
但若是经过Object.defineProperty, 咱们则能够
//这里只是简单设置下translateX的属性,其余如scale等属性可本身去尝试 Object.defineProperty(dom, 'translateX', { set: function(value) { var transformText = 'translateX(' + value + 'px)'; dom.style.webkitTransform = transformText; dom.style.transform = transformText; } //这样再后面调用的时候, 十分简单 dom.translateX = 10; dom.translateX = -10; //甚至能够拓展设置如scale, originX, translateZ,等各个属性,达到下面的效果 dom.scale = 1.5; //放大1.5倍 dom.originX = 5; //设置中心点X }
上面只是个简单的版本,并非最合理的写法,但主要是为了说明具体的意图和方法
增长属性获取和修改时的信息
如在Express4.0中,该版本去除了一些旧版本的中间件,为了让用户可以更好地发现,其有下面这段代码,经过修改get属性方法,让用户调用废弃属性时抛错并带上自定义的错误信息。
[ 'json', 'urlencoded', 'bodyParser', 'compress', 'cookieSession', 'session', 'logger', 'cookieParser', 'favicon', 'responseTime', 'errorHandler', 'timeout', 'methodOverride', 'vhost', 'csrf', 'directory', 'limit', 'multipart', 'staticCache', ].forEach(function (name) { Object.defineProperty(exports, name, { get: function () { throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.'); }, configurable: true }); });
当函数被用做事件处理函数时,它的 this 指向触发事件的元素(一些浏览器在使用非addEventListener 的函数动态添加监听函数时不遵照这个约定)。
// 被调用时,将关联的元素变成蓝色 function bluify(e){ console.log(this === e.currentTarget); // 老是 true // 当 currentTarget 和 target 是同一个对象是为 true console.log(this === e.target); this.style.backgroundColor = '#A5D9F3'; } // 获取文档中的全部元素的列表 var elements = document.getElementsByTagName('*'); // 将bluify做为元素的点击监听函数,当元素被点击时,就会变成蓝色 for(var i=0 ; i < elements.length; i++){ elements[i].addEventListener('click', bluify, false); }
当代码被内联on-event 处理函数调用时,它的this指向监听器所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());"> Show this </button>
上面的 alert 会显示 button 。注意只有外层代码中的 this 是这样设置的:
<button onclick="alert((function(){return this})());"> Show inner this </button>
在这种状况下,没有设置内部函数的 this,因此它指向 global/window 对象(即非严格模式下调用的函数未设置 this 时指向的默认对象)。
JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们容许切换函数执行的上下文环境(context),即 this 绑定的对象。不少 JavaScript 中的技巧以及类库都用到了该方法。让咱们看一个具体的例子:
function Point(x, y){ this.x = x; this.y = y; this.moveTo = function(x, y){ this.x = x; this.y = y; } } var p1 = new Point(0, 0); p1.moveTo(1, 1); console.log(p1.x,p1.y); //1 1 var p2 = {x: 0, y: 0}; p1.moveTo.apply(p2, [10, 10]); console.log(p2.x,p2.y); //10 10
在上面的例子中,咱们使用构造函数生成了一个对象 p1,该对象同时具备 moveTo 方法;使用对象字面量建立了另外一个对象 p2,咱们看到使用 apply 能够将 p1 的方法 apply 到 p2 上,这时候 this 也被绑定到对象 p2 上。另外一个方法 call 也具有一样功能,不一样的是最后的参数不是做为一个数组统一传入,而是分开传入的:
function Point(x, y){ this.x = x; this.y = y; this.moveTo = function(x, y){ this.x = x; this.y = y; } } var p1 = new Point(0, 0); p1.moveTo(1, 1); console.log(p1.x,p1.y); //1 1 var p2 = {x: 0, y: 0}; p1.moveTo.call(p2, 10, 10); // 只是参数不一样 console.log(p2.x,p2.y); //10 10
ECMAScript 5 引入了 Function.prototype.bind 。调用 f.bind(someObject) 会建立一个与 f 具备相同函数体和做用域的函数,可是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,不管这个函数是如何被调用的。
function f(){ return this.a; } //this被固定到了传入的对象上 var g = f.bind({a:"azerty"}); console.log(g()); // azerty var h = g.bind({a:'yoo'}); //bind只生效一次! console.log(h()); // azerty var o = {a:37, f:f, g:g, h:h}; console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
填坑
上面咱们已经讲了使用箭头函数填 setTimeout 的坑,此次咱们使用 bind 方法来试试:
var prop = 0; var Obj = { prop: 37, getProp: function() { setTimeout(function() { console.log(this.prop) // 37 }.bind(Obj),1000) } }; Obj.getProp();
一样能够填坑,可是看上去没有使用箭头函数来的优雅。
vue文档里的原话:
All lifecycle hooks are called with their 'this' context pointing to the Vue instance invoking it.
意思是:在Vue全部的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例。
示例分析
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script> <script src="https://unpkg.com/vue@2.5.9/dist/vue.js"></script> </head> <div id="app" style="width: 100%;height: auto;font-size:20px;"> <p id="id1"></p> <p id="id2"></p> </div> <script type="text/javascript"> var message = "Hello!"; var app = new Vue({ el:"#app", data:{ message: "你好!" }, created: function() { this.showMessage1(); //this 1 this.showMessage2(); //this 2 }, methods:{ showMessage1:function(){ setTimeout(function() { document.getElementById("id1").innerText = this.message; //this 3 }, 10) }, showMessage2:function() { setTimeout(() => { document.getElementById("id2").innerText = this.message; //this 4 }, 10) } } }); </script> </html>
示例定义了两个message。一个是全局变量,即window.message,它的值为英文“Hello!”。另一个是vue实例的数据message,它的值为中文的“你好!”。
运行示例,在浏览器获得:
第一个输出英文"Hello!”,第二个输出中文“你好!”。这说明了showMessage1()里的this指的是window,而showMessage2()里的this指的是vue实例。
//created created: function() { this.showMessage1(); //this 1 this.showMessage2(); //this 2 }
created函数为vue实例的钩子方法,它里面使用的this指的是vue实例。
//showMessage1() showMessage1:function(){ setTimeout(function() { document.getElementById("id1").innerText = this.message; //this 3 }, 10) }
对于普通函数(包括匿名函数),this指的是直接的调用者,在非严格模式下,若是没有直接调用者,this指的是window。showMessage1()里setTimeout使用了匿名函数,this指向window。
//showMessage2() showMessage2:function() { setTimeout(() => { document.getElementById("id2").innerText = this.message; //this 4 }, 10) }
箭头函数是没有本身的this,在它内部使用的this是由它定义的宿主对象决定。showMessage2()里定义的箭头函数宿主对象为vue实例,因此它里面使用的this指向vue实例。
为了不this指向出现歧义,有两种方法绑定this。
showMessage1()能够改成:
showMessage1:function(){ setTimeout(function() { document.getElementById("id1").innerText = this.message; //this 3 }.bind(this), 10) }
对setTimeout()里的匿名函数使用bind()绑定到vue实例的this。这样在匿名函数内的this也为vue实例。
showMessage1()也能够改成
showMessage1:function(){ var self = this; setTimeout(function() { document.getElementById("id1").innerText = self.message; //改成self }.bind(this), 10) }
这里吧表示vue实例的this赋值给变量self。在使用到this的地方改用self引用。
参考文章:
1.全面理解 JavaScript 中的 this
2.不会Object.defineProperty你就out了
3.10道典型的JavaScript面试题