一,使用this的五种情况
- 如果this出现在普通的函数中,this表示window,如果通过window打点调用一个函数,这个函数中的this也是window;
- 事件绑定、事件处理程序、事件发生时、浏览器帮我们调用这个函数,此函数中的this表示事件源;
- 在一个对象中,有一个方法(函数),如果通过对象调用了这个方法,那么这个方法中的this就表示这个对象;
- 在IIFE(立即调用函数)中,this表示window;
- 前四点都是在非严格模式下,在严格模式下,调用一个普通函数,this表示undefined,IIFE中的this也表示undefined;
二,以下实例中包含的五种情况
- this表示事件源
<button id="box">box</button>--> // 一个公用的button标签(供一下实例中引用)
<script>
let box = document.getElementById("box");
box.onclick = function () {
console.log(this); // this表示事件源
}
</script>
let box = document.getElementById("box");
function f(){
console.log(this); // window
return function g(){
console.log(this); // 事件源
}
}
box.onclick = f();
- this表示window
let box = document.getElementById("box");
function f() {
console.log(this); // window
}
// f(); 程序员自己调用一个函数 一个普通函数 函数中的this表示window
box.onclick = f();
function k() {
console.log(this); // window
}
window.k(); // window打点调用
k(); // 程序员自己调用 表示window中的
- this表示对象
var wc = {
name:"wangcai", // 叫属性
age:100, // 叫属性
eat:function () { // 叫方法(属性)
console.log("eat...")
console.log(this); 表示wc对象
}
} // 一个方法中的this是谁,就看点前面是谁。
wc.eat(); // 调用一个对象上的方法
- 在非严格模式下,this表示window
(function () {
console.log(this);
})(); // 在非严格模式下,IIFE中的this表示window
- 当启用严格模式(“use strict”)下
"use strict"; // 启用JS的严格模式
function f() {
console.log(this); //表示undefined
}
f();
(function () {
console.log(this); //表示undefined
})();
- 非严格模式下的几个实例:
function f() {
console.log(this.name); // 表示window
}
let box = document.getElementById("box");
box.name = "mybox";
var name = "mywindow";
var obj = {
name:"myobj",
f:f
}
f(); // mywindow
box.onclick = f; // 点击得到mybox //调用的是box对象中的属性
obj.f(); // myobj
box.onmousedown = function () { //onmousedown表示鼠标放button上面就要执行的
f(); // 普通函数调用
}
- onclick是在鼠标点击弹起之后触发的事件,即一次完整的鼠标点击过程。过程完成瞬间触发函数;
- onmousedown事件是在鼠标按键按下去的瞬间触发的;
- 简而言之,也就是说οnclick=onmousedown+onmouseup;
var num = 10; //60 65
var obj = {
num:20, // num:30
}
obj.fn = (function (num) {
this.num = num*3; //
num++; //
console.log(num) //21
return function (n) {
this.num += n; // 30
num++;
console.log(num) // 22 23
}
})(obj.num)// 输出21
var fn = obj.fn;
fn(5);// 输出22
obj.fn(10); //输出23 调用obj中的fn属性,其中fn指向function(n)的地址,里面的this.num中的this指向对象obj;
console.log(num,obj.num) //输出(65,30)
console.log(window.num) //输出 65
(function () {
var a = 1;
var obj = {
a:10, // 要使用这个a 必须是obj.a
f:function () {
a *= 2; // a为局部变量a不是对象obj中的;
}
}
obj.f();
alert(obj.a+a) // 运行结果为 12
})();
window.a = 1;
var obj = {
a:10,
f:function () {
this.a *= 2;
console.log(this.a) //this指对象obj
}
}
obj.f(); // 运行结果为 20
var name = "window";
var Wangcai = {
name:"Wangcai",
show:function () {
console.log(this.name)
},
f:function () {
var fun = this.show;
fun(); // 普通函数调用,对于普通函数调用,this就指向window
}
}
Wangcai.f(); //输出window,
var fullname = "language";
var obj = {
fullname:"javascript",
prop:{
getFullName:function () {
return this.fullname;
}
}
} // 谁用了getFullName 看 . 前面是什么, . 前面是obj.prop,说明getFullName中的this是obj.prop;然而obj.prop中没有fullname属性;所以输出undefined,因为访问一个对象上没有的属性,得到undefined;
console.log(obj.prop.getFullName()); // 输出 undefined
var fullname = "language";
var obj = {
fullname:"javascript",
prop:{
fullname:"vue",
getFullName:function () {
return this.fullname;
}
}
}
var t = obj.prop.getFullName;
console.log(t()); //就是简单的函数执行;输出language
let obj = {
fn:(function () {
return function () {
console.log(this)
}
})()
}
obj.fn(); //输出{fn: ƒ} fn()前面对应obj对象,所以在函数fn中的输出this指对象obj的;
let obj = {
fn:(function () {
return function () {
console.log(this)
}
})()
}
var t = obj.fn; //t所获取的是function () { console.log(this) }
t(); //只是简单的函数调用;
三, call,apply,bind使用的异同
首先说一下这三个方法的用途是用于修改this的指向的; 而且它们三个都可以修改;
- call的作用(1)可以改变this的指向;(2)让函数直接执行;(3)函数传参时,不是以数组的形式;
- apply的作用 (1)可以改变this的指向; (2)让函数直接执行;(3)函数传参时,以数组的形式;
- bind的作用 (1)可以改变this的指向; (2)不让函数执行;
那么问题来了:为什么要修改this指向?
从下面实例中来解释;
//存在两个堆,
var obj1 = {name:"wc",say:function () {console.log("我是:"+this.name) }}
var obj2 = {name:"xq",say:function () {console.log("我是:"+this.name) }}
obj1.say();
obj2.say();
- 以上代码存在的不足:同样的函数,但是占用了两个堆空间,造成了内存的浪费;
但我们有改进的方法:
(1)改进一
var abc = function (){console.log("我是:"+this.name) }
var obj1 = {name:"wc",say:abc} //此时say属性就指向堆空间;
var obj2 = {name:"xq",say:abc}
console.log(obj1.say === obj2.say)//输出结果为true,表明两个地址是一样的;
obj1.say();
obj2.say();
abc();
(2)改进二(使用call)
var abc = function (){console.log("我是:"+this.name) }
var obj1 = {name:"wc",say:abc}
var obj2 = {name:"xq",say:abc}
// call作用: 1)让abc中的this指向obj1 call改变this指向 2)让abc直接执行 调用
abc.call(obj1); // this==>obj1 abc()
abc.call(obj2); //
在调用toString()方法时,我们可以使用call方法去直接调用Object上面的toString方法,写法
var arr3 = [110];
Object.prototype.toString.call(arr3); // arr3直接调用Object的原型对象上面的toString
此时call:(1)call让this指向了Object.prototype,(2)让toString执行;总之,call可以让一个数据去借用一个方法;
利用Object.prototype.toString.call可以非常精确地检测一个数据的数据类型:
console.log(Object.prototype.toString.call(1)); // 此时会把1瞬间包装成对象
console.log(Object.prototype.toString.call("hello")); // 此时"hello"也瞬间包装成对象了
console.log(Object.prototype.toString.call(true))
var a;
console.log(Object.prototype.toString.call(a))
// call 1)改变toString中的this的指向 指向了{}
// 2)让toString执行
console.log(Object.prototype.toString.call({}))
console.log(Object.prototype.toString.call([]))
function f(){}
console.log(Object.prototype.toString.call(f))
let d = new Date()
console.log(Object.prototype.toString.call(d))
let r = new RegExp();
console.log(Object.prototype.toString.call(r))
下面代码可以将一段字符串截取出来,并且变为小写(用于将上面的检测类型给截取下来变为小写并输出):
function getType(abc) {
var rs = Object.prototype.toString.call(abc);
rs = rs.substr(8)
var len = rs.length
return rs.substr(0,len-1).toLowerCase()//变为小写
}
console.log(getType(123));//将数据类型修改输出
总之: call方法的两大作用如下
- 修改了this的指向;
- 可以让方法直接执行;
相对于call方法来说,apply有什么不一样呢?
可以说apply和call是一模一样的,唯一唯一的不同就是传递参数时候存在不一样;如下:
- 分别使用call和apply的时候:
function F(a,b) {
return a+b;
}
var obj = {};
F.call(obj); // 让F执行,关键是我们如何给F传递参数
F(1,2); // 我们自己调用F函数 并传递参数
console.log(F.call(obj,4,5)); // 我们使用call来传参的方式;
console.log(F.apply(obj,[4,5])); // 而使用apply来传参的方式;
- 再如:
var arr = [2,3,3,6,9,10,2,1];
// 获取上面的数组中的最大值 Math类(最好不要叫它为构造器)
// console.log(Math.max(2, 4, 1, 6));
console.log(Math.max.call(arr, 2, 3, 3, 6, 9, 10, 2, 1));//使用call传参
console.log(Math.max.apply(arr, [2,3,3,6,9,10,2,1]));//使用apply传参
console.log(Math.max.apply(arr, arr));//使用apply传参
console.log(Math.max.apply({}, arr));
console.log(Math.max.apply("", arr));
console.log(Math.max.apply(null, arr));
console.log(Math.max.apply("abc", arr));
//上面表明如使用apply方法的时候,不过第一个参数写啥,只要把实参传过去就OK,如传一个数值的话使用apply效果会好点;
对于bind方法来说,与上面两种方法不同的地方在于;
call 改变了this指向,并让函数执行;传递参数一个个传递;
apply 改变了this指向,并让函数执行;传送参数必须是数组;
bind 改变了this指向,但是函数不执行;
function F(a,b) {
console.log("F...")
return a+b;
}
var obj = {};
let k = F.bind(obj);//bind改变的知识this的指向,而函数不执行,要想执行还需要函数调用并传参;
console.log(k(1, 2));