var obj = { foo: function(){} } var foo = obj.foo; // 写法一 obj.foo(); // 写法二 foo();
虽然obj.foo和foo指向同一个函数,可是执行结果可能不同。javascript
var obj = { foo: function() { conosle.log(this.bar) }, bar: 2 }; var foo = obj.foo; var bar = 3; obj.foo(); // 2 foo(); // 3
这种差别的缘由就是由于内部使用了this
关键字,this
指向的是函数运行的所在环境,对于obj.foo()
来讲,this
执行obj
,对于foo()
来讲,this
指向window
全局环境html
JavaScript 语言之因此有this的设计,跟内存里面的数据结构有关系。java
var obj = {foo: 5}
也就是或变量obj是一个地址,后面读取obj.foo引擎先从obj拿到地址,而后再从该地址读取原始对象,返回它的属性值。
原始的对象以字典结构保存,每个属性名都对应一个属性描述对象。举例来讲,上面例子的foo属性,其实是如下面的形式保存的。面试
这样的结构是很清晰的,问题在于属性的值多是一个函数。json
var obj = { foo: function () {} };
这时,引擎会将函数单独保存在内存中,而后再将函数的地址赋值给foo属性的value属性。数组
因为函数是一个单独的值,因此它能够在不一样的环境(上下文)执行。数据结构
var f = function () {}; var obj = { f: f }; // 单独执行 f() // obj 环境执行 obj.f()
var f = function () { console.log(x); };
上面代码中,函数体里面使用了变量x。该变量由运行环境提供。app
如今问题就来了,因为函数能够在不一样的运行环境执行,因此须要有一种机制,可以在函数体内部得到当前的运行环境(context)。因此,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。函数
var f = function () { console.log(this.x); } var x = 1; var obj = { f: f, x: 2, }; // 单独执行 f() // 1 // obj 环境执行 obj.f() // 2
在obj环境执行,this.x指向obj.x。
函数f在全局环境执行,this.x指向全局环境的x。post
回到咱们最初的问题 obj.foo()
是经过obj找到foo,因此就是在obj
环境执行。一旦var foo = obj.foo
,变量foo
就直接指向函数自己,因此foo()
就变成在全局环境执行。
this在js中一直是谜同样的存在着,在面试中也是常常会被问道
this的指向在函数建立的时候是决定不了的,在调用的时候才能决定
this; //在全局范围内使用`this`,它将会指向全局对象 var name="zhoulujun"; function say(){ console.log(this.name) } say(); //zhoulujun
当执行 say函数的时候, JavaScript 会建立一个 Execute context (执行上下文),执行上下文中就包含了 say函数运行期所须要的全部信息。 Execute context 也有本身的 Scope chain, 当函数运行时, JavaScript 引擎会首先从用 say函数的做用域链来初始化执行上下文的做用域链。
foo(); //this指向全局对象
test.foo(); //this指向test对象
new foo(); //函数与new一块使用即构造函数,this指向新建立的对象
function foo(a, b, c) {} var bar = {}; foo.apply(bar, [1, 2, 3]); //this被设置成bar foo.call(bar, 1, 2, 3); //this被设置成bar
实例1
myObj3={ site:"zhoulujun.cn", andy:{ site:"www.zhoulujun.cn", fn:function(){ console.log(this) console.log(this.site) } } }; var site="111"; var fn=myObj3.andy.fn; fn(); // 这里的调用环境是window // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} // 111
实例2
myObj3={ site:"zhoulujun.cn", andy:{ site:"www.zhoulujun.cn", fn:function(){ console.log(this) console.log(this.site) } } }; var site="111"; myObj3.andy.fn(); VM51:6 {site: "www.zhoulujun.cn", fn: ƒ} VM51:7 www.zhoulujun.cn
实例3
document.getElementById( 'div1' ).onclick = function(){ console.log( this.id );// 输出: div1 var func = function(){ console.log ( this.id );// 输出: undefined } func(); }; //修正后 document.getElementById( 'div1' ).onclick = function(){ var func = function(){ console.log ( this.id );// 输出: div1 } func.call(this); };
实例4
var A = function( name ){ this.name = name; }; var B = function(){ A.apply(this,arguments); }; B.prototype.getName = function(){ return this.name; }; var b=new B('sven'); console.log( b.getName() ); // 输出: 'sven'
实例5
function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); // 2
由于apply、call存在于Function.prototype中,因此每一个方法都有这两个属性。
call
函数名.call(对象,arg1....argn) //功能: //1.调用函数 //2.将函数内部的this指向第一个参数的对象 //3.将第二个及之后全部的参数,做为实参传递给函数
apply主要用途是直接用数组传参
函数名.apply(对象, 数组/伪数组); //功能: //1.调用函数 //2.将函数内部的this指向第一个参数的对象 //3.将第二个参数中的数组(伪数组)中的元素,拆解开依次的传递给函数做为实参 //案例求数组中最大值 var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] ); console.log(a);// 输出:5
call应用(将伪数组转为数组)
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 } Array.prototype.join.call(arrayLike, '&'); // name&age&sex Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] // slice能够作到类数组转数组 Array.prototype.map.call(arrayLike, function(item){ return item.toUpperCase(); }); // ["NAME", "AGE", "SEX"] console.log( Object.prototype.toString.call(num), Object.prototype.toString.call(str), Object.prototype.toString.call(bool), Object.prototype.toString.call(arr), Object.prototype.toString.call(json), Object.prototype.toString.call(func), Object.prototype.toString.call(und), Object.prototype.toString.call(nul), Object.prototype.toString.call(date), Object.prototype.toString.call(reg), Object.prototype.toString.call(error) ); // '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]' // '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]'