1.2 This
this的基础四点
-
函数预编译环节 this->window
-
全局作用域 this->window
-
call/apply 改变函数运行时this指向
-
obj.function() 谁调用方法,this指向谁
var name = "222";
var a ={
name:"111";
say:function(){
log(this.name);
}
}
var fun = a.say; //函数指针赋给fun
fun(); //222 //在全局环境执行,this指向window
a.say(); //111 a调用,指向a
var b = {
name:"333",
say:function(fun){
fun();
}
}
b.say(s.say); //222 s.say放入b中执行,无人调用,走预编译环节
b.say = a.say;
b.say(); //333
函数上下文
普通函数调用模式
this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。
var a = 10;
var obj = {
a: 20
}
function fn() {
console.log(this.a);
}
fn(); // 10
fn.call(obj); // 20
在函数执行过程中,this一旦被确定,就不可更改了。
var a = 10;
var obj = {
a: 20
}
function fn() {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}
fn();
对象中的函数调用模式
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。
- 调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。
- 如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
console.log(obj.c); //40
console.log(obj.fn()); //10
var name = 'window';
var doSth = function(){
console.log(this.name);
}
var student = {
name: '甲生',
doSth: doSth,
other: {
name: 'other',
doSth: doSth,
}
}
student.doSth(); // '甲生'
student.other.doSth(); // 'other'
// 用call类比则为:
student.doSth.call(student);
// 用call类比则为:
student.other.doSth.call(other);
箭头函数中的调用模式
- 箭头函数的
this,总是指向定义时所在的对象,而不是运行时所在的对象 。
//1
function foo() {
setTimeout( () => {
console.log("id:", this.id);
},100);
}
var id = 21;
foo.call( { id: 42 } ); //id:42
//2
function foo() {
setTimeout( function (){
console.log("id:", this.id);
},100);
}
var id = 21;
foo.call( { id: 42 } ); //id:21
-
如果箭头函数被非箭头函数包含,则
this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象 -
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
var name = 'window';
var student = {
name: '甲生',
doSth: function(){
// var self = this;
var arrowDoSth = () => {
// console.log(self.name);
console.log(this.name);
}
arrowDoSth();
},
arrowDoSth2: () => {
console.log(this.name);
}
}
student.doSth(); // '甲生'
student.arrowDoSth2(); // 'window'
- 无法通过
call、apply、bind绑定箭头函数的this(它自身没有this)。而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this
//1
var student = {
name: '甲生',
doSth: function(){
console.log(this.name);
return () => {
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
//还是student调用,箭头函数的 this 绑定了上层普通函数的 this
// '甲生' Object { name: "甲生", doSth: doSth() } 'arrowFn:' '甲生'
student.doSth().call(person);
student.doSth.call(person) //person
//person 调用
// 'person' Object { name: "person" } 'arrowFn:' 'person'
student.doSth.call(person)();
//2
//return 的不是箭头函数时
var student = {
name: '甲生',
doSth: function(){
console.log(this.name);
return function (){
console.log(this);
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
//doSth()执行 call() 执行return的函数 所以this 指向person
student.doSth().call(person); // '甲生' Object { name: "person" } 'arrowFn:' 'person'
//call() 执行doSth this指向person 最后一个() 执行return的函数、全局执行
student.doSth.call(person)(); // 'person' Window 'arrowFn:' '<empty string>'
//3
var name = "asdas";
var student = {
name: '甲生',
doSth: function(){
console.log(this.name);
return function (){
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
student.doSth().call(person); // '甲生' 'arrowFn:' 'person'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'asdas'
call/apply/bind 调用模式
这三个方法都是Function自带的
call
语法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
thisArg :使用call方法指定this指向时注意一下几种情况
- 不传,或者传
null,undefined, 函数中的this指向window对象 - 传递另一个函数的函数名,函数中的
this指向这个函数的引用,并不一定是该函数执行时真正的this值 - 值为原始值(数字,字符串,布尔值)的
this会指向该原始值的自动包装对象,如String、Number、Boolean - 传递一个对象,函数中的this指向这个对象
function a(){
console.log(this);
}
function b(){}
var obj = {name:'甲生'};
a.call() === a(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number { 1 }
a.call(''); //String {""}
a.call(true); //Boolean {true}
a.call(b);// function b(){}
a.call(obj); //Object { name: "甲生" }
apply
语法:
fun.apply(thisArg[, argsArray])
bind
bind是返回一个绑定函数可稍后执行,call、apply是立即调用
- 当点击网页时,
EventClick被触发执行,输出JSLite.io p1 p2, 说明EventClick中的this被bind改变成了obj对象。 - 如果你将
EventClick.bind(obj,'p1','p2')变成EventClick.call(obj,'p1','p2')的话,页面会直接输出JSLite.io p1 p2
var obj = {name:'JSLite.io'};
/**
* 给document添加click事件监听,并绑定EventClick函数
* 通过bind方法设置EventClick的this为obj,并传递参数p1,p2
*/
document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function EventClick(a,b){
console.log(
this.name, //JSLite.io
a, //p1
b //p2
)
}
// JSLite.io p1 p2
构造函数中的 this
-
var obj = new Base();var obj = {}; 1、创建一个空对象 obj.__proto__ = Base.prototype; 2、将空对象的_proto_指向Base函数的prototype Base.call(obj); 3、改变this指向新实例的内部包含一个指向构造函数原型对象的指针(-proto-)
-
如果函数没有返回对象类型
Object(包含Functoin,Array,Date,RegExg,Error),那么new表达式中的函数调用会自动返回这个新的对象。
new 操作符调用时,this 指向生成的新对象。 特别提醒一下,new调用时的返回值,如果没有显式返回对象或者函数,才是返回生成的新对象。
function Student(name){
this.name = name;
// return function f(){};
// return {};
}
var result = new Student('甲生');
console.log(result);
// 如果返回函数f,则result是函数f,如果是对象{},则result是对象{}
//没有 return 时,才是 { name:"甲生" }
原型链中的 this
function Student(name){
this.name = name;
}
var s1 = new Student('甲生');
Student.prototype.doSth = function(){
console.log(this.name);
}
s1.doSth(); // '甲生'
这就是对象上的方法调用模式。自然是指向生成的新对象。 如果该对象继承自其它对象。同样会通过原型链查找。
从ECMAScript 解读 this
Reference简介
ECMAScript 的类型分为 语言类型 和 规范类型
- 语言类型: Undefined, Null, Boolean, String, Number, 和 Object。
- 规范类型:相当于meta-values, 用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference(引用), List, Completion, Property Descriptor(属性描述符), Property Identifier(属性标识符), Lexical Environment(词法环境), 和 Environment Record(环境记录)。
1、Reference组成部分
- base value
- 属性所在对象或者就是 Environment Record(环境记录)
- 值只可能是 undefined, Object, Boolean, String, Number, or Environment Record 其中的一种。
- reference name
- 属性名称
- strict reference
- 布尔值严格引用标记
2、获取 Reference 组成部分方法
- GetBas() 返回 reference 的 base value。
- IsPropertyReference () 如果 base value 是一个对象,就返回true。
- GetValue() 调用 GetValue,返回的将是具体的值,而不再是一个 Reference
//例1
var foo = 1;
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
//例2
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
如何确定 this 的值
当函数调用时如何确定 this 的值
- 计算 MemberExpression 的结果赋值给 ref
- 判断 ref 是不是一个 Reference 类型
- true,
- 如果 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
- 如果 IsPropertyReference(ref) 是 false,base value 值是 Environment Record, 那么 this 的值为 ImplicitThisValue(ref) (该方法始终返回 undefined)
- false
- 那么 this 的值为 undefined
MemberExpression :
- PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
- FunctionExpression // 函数定义表达式
- MemberExpression [ Expression ] // 属性访问表达式
- MemberExpression . IdentifierName // 属性访问表达式
- new MemberExpression Arguments // 对象创建表达式
function foo() {
console.log(this)
}
foo(); // MemberExpression 是 foo
function foo() {
return function() {
console.log(this)
}
}
foo()(); // MemberExpression 是 foo()
var foo = {
bar: function () {
return this;
}
}
foo.bar(); // MemberExpression 是 foo.bar
简单理解: MemberExpression 其实就是()左边的部分
最后的例子
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//示例1
console.log(foo.bar());
var Reference = {
base: foo,
name: 'bar',
strict: false
};
this = GetBase(ref) = foo; //2
//示例2
console.log((foo.bar)()); //2
//因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined
//示例3
console.log((foo.bar = foo.bar)()); //1
//示例4
console.log((false || foo.bar)()); //1
//示例5
console.log((foo.bar, foo.bar)()); //1