this
在全局作用域下,this代表window
<script>
console.log(this)
</script>
//如果在全局下这样写
var a = 1
var b = 2
//就相当于window的属性
window.a
//返回1
window.b
//返回2
window.a === this.a //true
作为函数调用
在函数被直接调用时this绑定到全局对象。在浏览器中,window 就是该全局对象
console.log(this);
function fn1(){
console.log(this);
}
fn1();
//两个this都是window
相当于:
var a = 1
function fn1(){
console.log(a)
}
fn1()
//调用fn1,输出a,函数中没有a的值,去外面查找,找到了a = 1
function fn1(){
var b = 2
console.log(this.b)
}
fn1()
//这样输出undefined,因为全局下没有b
内部函数
函数嵌套产生的内部函数的this不是其父函数,仍然是全局变量
function fn0(){
function fn(){
console.log(this);
}
fn();
}
fn0();
setTimeout、setInterval
这两个方法执行的函数this也是全局对象
document.addEventListener('click', function(e){
console.log(this); //这个this是document
setTimeout(function(){
console.log(this); //这个this是全局对象
}, 200);
}, false);
如果想使用上面的this
document.addEventListener('click', function(e){
console.log(this); //这个this是document
var _this = this
setTimeout(function(){
console.log(_this); //就是上面的this了
}, 200);
}, false);
作为构造函数调用
所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象
new 运算符接受一个函数 F 及其参数:new F(arguments...)。这一过程分为三步:
- 创建类的实例。这步是把一个空的对象的 proto 属性设置为 F.prototype 。
- 初始化实例。函数 F 被传入参数并调用,关键字 this 被设定为该实例。
- 返回实例。
看例子
function Person(name){
this.name = name;
}
Person.prototype.printName = function(){
console.log(this.name);
};
var p1 = new Person('Byron');
var p2 = new Person('Casper');
var p3 = new Person('Vincent');
p1.printName();
p2.printName();
p3.printName();
作为对象方法调用
在 JavaScript 中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,this 被自然绑定到该对象
var obj1 = {
name: 'Byron',
fn : function(){
console.log(this);
}
};
obj1.fn(); //this是obj1,谁调用,this就是谁
var obj2 = {
name: 'Byron',
obj3:{
fn : function(){
console.log(this);
}
}
}
obj2.obj3.fn(); //this是obj3
//小陷阱
var fn2 = obj1.fn
fn2() //fn2是全局变量,相当于widow.fn2(),所以this代表window
DOM对象绑定事件
在事件处理程序中this代表事件源DOM对象(低版本IE有bug,指向了window)
document.addEventListener('click', function(e){
console.log(this); //this是document
var _document = this;
setTimeout(function(){
console.log(this); //window
console.log(_document); //document
}, 200);
}, false);
Function.prototype.bind
bind,返回一个新函数,并且使函数内部的this为传入的第一个参数
var obj1 = {
name: 'Byron',
fn : function(){
console.log(this);
}
};
var obj3 = { a:3 };
var fn3 = obj1.fn.bind(obj3);
fn3(); //obj3
//改变一下代码,让setTimeout中的this也是document
document.addEventListener('click', function(e){
console.log(this); //this是document
setTimeout(function(){
console.log(this); //this是document
}.bind(this), 200); //添加bind(this),bind得到一个新的函数,函数里面的this,是传递的对象
}, false);
使用call和apply设置this
call apply,调用一个函数,传入函数执行上下文及参数
fn.call(context, param1, param2...)
fn.apply(context, paramArray)
语法很简单,第一个参数都是希望设置的this对象,不同之处在于call方法接收参数列表,而apply接收参数数组
fn2.call(obj1);
fn2.apply(obj1);
举例
var value = 100
function fn4(a,b){
console.log(this.value + a + b) //this是全局的window
}
fn4(3,4) //107
//----------------------
var obj4 = {
value: 200
}
function fn4(a,b){
console.log(this.value + a + b)
}
//call的用法
fn4.call(obj4,3,4) //this就是obj4,第一个参数
//apply的用法
fn4.apply(obj4,[3,4]) //把后面数组的东西,作为函数的参数
求最大值
var arr = [1,2,7,4]
Math.max(1,2,7,4) //这样才能得到最大值
使用apply方法实现
console.log(Math.max.apply(null,arr))
arguments
arguments是一个类数组对象,不是一个数组,没有数组的方法。
[].join() //数组里面有个join的方法。
如果这样写
function joinStr(){
return arguments.join('-')
}
joinStr('a','b','c')
会报错,arguments不是一个函数,是一个对象。没有join方法
正确方法在下面
在函数调用时,会自动在该函数内部生成一个名为 arguments的隐藏对象
该对象类似于数组,可以使用[]运算符获取函数调用时传递的实参
只有函数被调用时,arguments对象才会创建,未调用时其值为null
function fn5(name, age){
console.log(arguments);
name = 'XXX';
console.log(arguments);
arguments[1] = 30;
console.log(arguments);
}
fn5('Byron', 20);
方法一
function joinStr(){
console.log(Array.prototype.join.call(arguments,'-')) //join() 将数组转化为字符串
//[].join.call === Array.prototype.call 没有区别。[]是Array的一个实例。
}
joinStr('a','b','c') //a-b-c
方法二
function joinStr(){
var join = Array.prototype.join.bind(arguments)
console.log(join('-'))
}
joinStr('a','b','c') //a-b-c
函数的执行环境
JavaScript中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因
一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数
接下来创建作用域链,然后初始化变量。首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。
如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解JavaScript中的变量作用域非常重要,最后为this变量赋值,会根据函数调用方式的不同,赋给this全局对象,当前对象等
至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取
三种变量
实例变量:(this)类的实例才能访问到的变量
静态变量:(属性)直接类型对象能访问到的变量
私有变量:(局部变量)当前作用域内有效的变量
看个例子
function ClassA(){
var a = 1; //私有变量,只有函数内部可以访问
this.b = 2; //实例变量,只有实例可以访问
}
ClassA.c = 3; // 静态变量,也就是属性,类型访问
console.log(a); // error
console.log(ClassA.b) // undefined
console.log(ClassA.c) //3
var classa = new ClassA();
console.log(classa.a);//undefined
console.log(classa.b);// 2
console.log(classa.c);//undefined
更多关于 this 的文章
原型链
回顾一下类、实例、prototype、__proto__的关系
function Person(nick, age){
this.nick = nick;
this.age = age;
}
Person.prototype.sayName = function(){ //函数都有一个prototype属性,这个属性是一个对象。sayName是绑定的方法。
console.log(this.nick);
}
var p1 = new Person('Herbert',25);
var p1 = new Person('Jack',25);
//new分三步
//创建一个新的对象
//执行这个函数,里面的this代表刚刚创建的对象
//把刚刚创建的对象返回。
//所有对象的__proto__都指向prototype
p1.sayName();
p2.sayName();
- 我们通过函数定义了类Person,类(函数)自动获得属性prototype
- 每个类的实例都会有一个内部属性proto,指向类的prototype属性
p1.__proto__.constructor === Person //true
Person.prototype.constructor === Person //true
p1.constructor === Person //true 发现本身没有,就会从__proto__中查找。
p1.toString() //[object Object] 这个属性又是从哪里来的?
//从p1.__proto__.__proto__中找到的 (原型链)
如果给他赋值,就会覆盖,再调用时就直接获取,不去__proto__中查找
p1.toString = function(){congsole.log(this)}
有趣的现象
我们定义一个数组,调用其valueOf方法
var a = [1,2,3] //字面量创建数组
new Array(1,2,3) //一样的效果 Array是一个构造函数,1,2,3是传的参数
a.join('--') //"1--2--3" 哪来的?就是Array.prototype里面的
[1, 2, 3].valueOf(); // [1, 2, 3]
很奇怪的是我们在数组的类型Array中并不能找到valueOf的定义,根据之前的理论那么极有可能定义在了Array的prototype中用于实例共享方法,查看一下
我们发现Array的prototype里面并未包含valueOf等定义,那么valueOf是哪里来的呢?
一个有趣的现象是我们在Object实例的__proto__属性(也就是Object的prototype属性)中找到了找到了这个方法
那么Array的实例为什么同样可以查找到Object的prototype里面定义的方法呢?
查找valueOf过程
因为任何类的prototype属性本质上都是个类Object的实例,所以prototype也和其它实例一样也有个proto内部属性,指向其类型Object的prototype
我们大概可以知道为什么了,自己的类的prototype找不到的话,还会找prototype的类型的prototype属性,这样层层向上查找
大概过程是这样的
记当前对象为obj,查找obj属性、方法,找到后返回
没有找到,通过obj的proto属性,找到其类型Array的prototype属性(记为prop)继续查找,找到后返回
-
没有找到,把prop记为obj做递归重复步骤一,通过类似方法找到prop的类型Object的 prototype进行查找,找到返回
这就是传说中的原型链,层层向上查找,最后还没有就返回undefined
类型
instanceof操作符,判断一个对象是不是某个类型的实例
[1, 2, 3] instanceof Array; //true
//判断[1, 2, 3].__proto__是否等于Array.prototype
//如果不是。再看[1, 2, 3].__proto__.__proto__是否等于Array.prototype
//一直往下找,直到找到,或者__proto__为null
可以看到[1, 2, 3]是类型Array的实例
[1, 2, 3] instanceof Object; //true
这个结果有些匪夷所思,怎么又是Array的实例,又是Object的实例,这不是乱套了
其实这个现象在日常生活中很常见其实,比如我们有两种类型
- 类人猿
- 动物
我们发现黑猩猩既是类人猿这个类的物种(实例),也是动物的实例
是不是悟出其中的门道了,类人猿是动物的一种,也就是说我们的两个类型之间有一种父子关系
这就是传说中的继承,JavaScript正是通过原型链实现继承机制的
继承
继承是指一个对象直接使用另一对象的属性和方法。
JavaScript并不提供原生的继承机制,我们自己实现的方式很多,介绍一种最为通用的
通过上面定义我们可以看出我们如果实现了两点的话就可以说我们实现了继承
- 得到一个类的属性
- 得到一个类的方法
分开讨论一下,先定义两个类
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.printName = function(){
console.log(this.name);
};
function Male(sex){
this.sex = sex;
}
Male.prototype.printAge = function(){
console.log(this.age);
};
假设需要定义一个角色,如果把基本属性都写一遍很繁琐。如果以后基本属性改变,这个角色也需要更改。
使用继承的原因:在原来定义好的基础上,增加一点自己的东西。需要借用以前的东西。
例子:
function Person(name,age){
this.name = name
this.age = age
}
//可以说话
Person.prototype.sayName = function(){
console.log('My name is ' + this.name)
}
//可以行走
Person.prototype.walk = function(){
console.log(this.name + ' is walking')
}
//构造一个人
var p = new Person('jack',20)
function Student(name,age,sex){
第一步:把属性继承过来
//Person.call(this,name,age) //和下面方法一样
Person.bind(this)(name,age) //执行Person函数 重点! 这种方法只能继承属性,不能继承方法。
第二步:继承方法
//Student.prototype.__proto__ = Person.prototype 使用下面的方法
this.sex = sex
}
//继承方法
Student.prototype = Object.create(Person.prototype)
//如果不使用上面这句,可以使用以下代码代替,要注意次序,在下面代码之前。
//fn.prototype = Person.prototype
//function fn(){}
//Student.prototype = new fn()
Student.prototype.constructor = Student //指向自身,不写则指向Person
//s instanceof Student true
//s instanceof Person true
//s.__proto__.__proto__ === Person.prototype true
//增加的新属性
Student.prototype.doing = function(){
console.log('I an studing')
}
var s = new Student('mack',2,'boy')
属性获取
对象属性的获取是通过构造函数的执行,我们在一个类中执行另外一个类的构造函数,就可以把属性赋值到自己内部,但是我们需要把环境改到自己的作用域内,这就要借助我们讲过的函数call了
改造一些Male
function Male(name, sex, age){
Person.call(this, name, sex);
this.age = age;
}
Male.prototype.printAge = function(){
console.log(this.age);
};
实例化看看结果
var m = new Male('Byron', 'male', 26);
console.log(m.sex); // "male"
方法获取
我们知道类的方法都定义在了prototype里面,所以只要我们把子类的prototype改为父类的prototype的备份就好了
Male.prototype = Object.create(Person.prototype);
//Object.create() 方法会使用指定的原型对象及其属性去创建一个新的对象。
Object.create() - JavaScript | MDN
例子:
var a = Object.create({b:1}) //创建一个对象,对象的原型是{b:1}
a //输出Object,里面什么都没有,在__proto__下有b:1
这里我们通过Object.createclone了一个新的prototype而不是直接把Person.prtotype直接赋值,因为引用关系,这样会导致后续修改子类的prototype也修改了父类的prototype,因为修改的是一个值
另外Object.create是ES5方法,之前版本通过遍历属性也可以实现浅拷贝
这样做需要注意一点就是对子类添加方法,必须在修改其prototype之后,如果在之前会被覆盖掉
Male.prototype.printAge = function(){
console.log(this.age);
};
Male.prototype = Object.create(Person.prototype);
这样的话,printAge方法在赋值后就没了,因此得这么写
function Male(name, sex, age){
Person.call(this, name, sex);
this.age = age;
}
Male.prototype = Object.create(Person.prototype);
Male.prototype.printAge = function(){
console.log(this.age);
};
这样写貌似没问题了,但是有个问题就是我们知道prototype对象有一个属性constructor指向其类型,因为我们复制的父元素的prototype,这时候constructor属性指向是不对的,导致我们判断类型出错
Male.prototype.constructor; //Person
因此我们需要再重新指定一下constructor属性到自己的类型
最终方案
我们可以通过一个函数实现刚才说的内容
function inherit(superType, subType){
var _prototype = Object.create(superType.prototype);
_prototype.constructor = subType;
subType.prototype = _prototype;
}
使用方式
function Person(name, sex){
this.name = name;
this.sex = sex;
}
Person.prototype.printName = function(){
console.log(this.name);
};
function Male(name, sex, age){
Person.call(this, name, sex);
this.age = age;
}
inherit(Person, Male);
// 在继承函数之后写自己的方法,否则会被覆盖
Male.prototype.printAge = function(){
console.log(this.age);
};
var m = new Male('Byron', 'm', 26);
m.printName();
这样我们就在JavaScript中实现了继承
hasOwnProperty
可能会有一个疑惑,继承之后Male的实例也有了Person的方法,那么怎么判断某个是自己的还是父类的?
hasOwnPerperty是Object.prototype的一个方法,可以判断一个对象是否包含自定义属性而不是原型链上的属性,hasOwnProperty是JavaScript中唯一一个处理属性但是不查找原型链的函数
m.hasOwnProperty('name'); // true
m.hasOwnProperty('printName'); // false
Male.prototype.hasOwnProperty('printAge'); // true