this关键字可以用在构造函数之中,表示实例对象。除此之外还可以用在别的场合。但是不管是什么场合,this都有一个共同点:它总是返回一份对象
函数在浏览器全局环境下被简单调用,在非严格模式下 this 指向 window,在通过 use strict 指明严格模式的情况下指向 undefined。
例1:
function f1() {
console.log(this);
}
function f2() {
'use strict'
console.log(this);
}
f1() // window or global
f2() // undefined
例2:
const foo = {
bar : 10,
fn : function(){
console.log(this);
console.log(this.bar);
}
}
var fn1 = foo.fn;
fn1();
// window or global
// undefined
虽然fn函数在foo对象中作为该对象的一个方法,但是赋值给fn1之后,fn1仍然是在window的全局环境下执行的。因此this指向的还是window
例3:
const foo = {
bar : 10,
fn : function(){
console.log(this);
console.log(this.bar);
}
}
foo.fn();
// { bar: 10, fn: [Function: fn] }
// 10
这里,this指向调用它的对象,在foo.fn() 语句中,this指向的是foo对象
一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
例4:
const student = {
name: 'zhangsan',
fn: function () {
return this;
}
}
console.log(student.fn() === student);
// true
this 指向当前的对象student
在嵌套关系中,this指向最后调用它的对象
例5:
const student = {
name: 'zhangsan',
son: {
name: 'zhangxiaosan',
fn: function () {
return this.name
}
}
}
console.log(student.son.fn());
// zhangxiaosan
高阶-例6:
const o1 = {
text: 'o1',
fn: function () {
return this.text;
}
}
const o2 = {
text: 'o2',
fn: function () {
return o1.fn();
}
}
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn;
return fn();
}
}
console.log(o1.fn());
console.log(o2.fn());
console.log(o3.fn());
// o1
// o1
// undefined
o1.fn() this指向调用它的对象,打印为o1;
o2.fn() this 指向为o1,打印为o1;
o3.fn() 这里是将o1.fn 赋值给fn,并return,所以在调用的时候,相当于全局调用function () { return this.text; },并不是以对象的形式调用,this指向window,所以打印为undefined
问题:在一个div节点的时间函数内部,有一个局部的callback方法,我们希望callback方法内部的this指向div节点
<div id="div1">我是一个div</div>
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log('this1',this.id);
const callback = function(){
console.log(this.id);
}
callback();
}
由于callback作为普通函数被调用,所以this指向为window
解决: 通过变量保存的方式
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log('this1',this.id); // div1
const that = this; // 保存当前 this 的指向
const callback = function(){
console.log(that.id);
}
callback();
}
箭头函数中的this 指向始终是指向的外层作用域(箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域)
var x = 20;
const obj = {
x: 10,
test: () => {
console.log(this); // {}
console.log(this.x); // undefined
}
}
obj.test();
箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。
var name = "JavaScript";
const obj = {
name: "PHP",
test: function () {
const i = function () {
console.log(this.name);
// i 是以函数的形式被调用的,所以 this 指向全局
// 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
}
i();
}
}
obj.test(); // JavaScript
// 改为箭头函数:
var name = "JavaScript";
const obj = {
name : "PHP",
test : function(){
const i = ()=>{
console.log(this.name);
// 由于 i 为一个箭头函数,所以 this 是指向外层的
// 所以 this.name 将会打印出 PHP
}
i();
}
}
obj.test();// PHP
另外箭头函数 不能作为构造函数
const Test = (name, age) => {
this.name = name;
this.age = age;
};
const test = new Test("xiejie", 18);
// TypeError: Test is not a constructor
call方法可以指定this指向(即函数执行时所在的作用域),然后在指定的作用域中执行函数。
// 函数.call(对象)
fun.call(thisArg, arg1, arg2, ...)
thisArg:在 fun函数运行时指定的 this值 。如果参数为空或 null、undefind,则默认传参全局对象,同时值为原始值(数字,字符串,布尔值)的 this会指向该原始值的自动包装对象。
例1:改变this指向
var obj = {};
var f = function(){
return this;
};
console.log(f() === window);
console.log(f.call(obj) === obj)
执行f() 时,因为在全局 环境下执行,所以this指向window,通过call 传入第一个参数,call前面的函数执行时,this指向为第一个参数对象
例2:this指向传入undefined 、null
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 传入空,指向全局 ,123
a.call(null) //传入null 指向全局, 123
a.call(undefined) //传入undefined, 123
a.call(window) // 123
a.call(obj) //传入obj,this指向obj 456
例3: 传入Number类型,this指向包装对象
var f = function () {
return this;
};
f.call(5); // Number {[[PrimitiveValue]]: 5}
第一个参数是 this 指向的对象,之后的是函数回调所需的参数
例4:
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
hasOwnProperty 该方法是查看一个对象是否有某一个属性或者方法
这个属性或者方法必须是自身就有的,而不是继承而来
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
上面代码中 hasOwnProperty 是 obj 继承来的方法,用来判断对象是否包含自身特点(非继承)属性,但是 hasOwnProperty 并不是保留字,如果被对象覆盖,会造成结果错误。
call 方法可以解决这个问题,它将 hasOwnProperty 方法的原始定义放到 obj 对象上执行,这样无论 obj 上有没有同名方法,都不会影响结果。
func.apply(thisValue, [arg1, arg2, ...])
相同点:
不同点:
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
1. 输出数组的最大值
var a = [24,30,2,33,1]
Math.max.apply(null,a) //33
2. 将数组的空元素转化成undefined
意义:数组的 forEach 方法会跳过空元素,但是不会跳过 undefined。undefined可以通过forEach循环出来
var a = ['a', , 'b'];
function print(i) {
console.log(i);
}
a.forEach(print)
// a
// b
Array.apply(null, a).forEach(print)
// a
// undefined
// b
3. 配合数组的slice方法,实现类数组转化为真正的数组
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
将类似数组的对象转化为数组
f.bind(obj)
bind用于将函数体内的this绑定到某个对象,然后返回一个新函数:
例:
问题:
var d = new Date();
d.getTime() // 1481869925657
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.
执行print时,将getTime赋值给print,相当于全局调用,此时this指向window
使用bind解决:
var print = d.getTime.bind(d);
print() // 1481869925657
通过bind,返回一个新函数,这个新函数this被绑定到了d上
bind接收的参数就是所要绑定的对象
常规绑定:
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
bind中传入对象为counter,所以bind前函数执行时this指向counter
绑定到其他对象:
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var obj = {
count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101
传入的对象为obj,所以执行bind前的函数时,函数指向obj
bind 接受多个参数时,除了第一个参数,其他参数会绑定到原函数的参数
var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5);
newAdd(5) // 20
bind的第一个参数为obj,所以将bind前函数的this指向为obj,后面的参数会作为调用add方法时的参数传入
若 bind 方法的第一个参数是 null 或 undefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。
function add(x, y) {
return x + y;
}
var plus5 = add.bind(null, 5);
plus5(10) // 15
函数add内没有使用this,bind方法主要目的是绑定x,y参数,每次运行plus5时,只需要传入另一个参数y就行了。
事件监听时:
由于每次运行时,就会返回一个新函数。所以上面的代码click事件绑定bind方法会生成一个匿名函数。导致无法取消绑定
element.addEventListener('click', o.m.bind(o));
//取消绑定时
element.removeEventListener('click', o.m.bind(o));
解决:
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
var counter = {
count: 0,
inc: function () {
'use strict';
this.count++;
}
};
function callIt(callback) {
callback();
}
// 写法1: callIt(counter.inc())
callIt(counter.inc.bind(counter));
counter.count // 1
使用如上写法1 的方式传入函数,调用时,this会指向window,相当于全局调用。解决方式就是通过bind绑定this
追问:某些数组方法接收的函数中的this指向
var obj = {
name: '张三',
times: [1, 2, 3],
print: function () {
this.times.forEach(function (n) {
console.log(this.name);
});
}
};
obj.print()
如上代码没有任何输出,因为this指向为window
解决:
obj.print = function () {
this.times.forEach(function (n) {
console.log(this.name);
}.bind(this));
};
obj.print()
// 张三
// 张三
// 张三
利用 bind 方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的 slice 方法为例。
[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
这样做的本质是在 [1, 2, 3] 上面调用 Array.prototype.slice 方法,因此可以用 call 方法表达这个过程,得到同样的结果。
call 方法实质上是调用 Function.prototype.call 方法,因此上面的表达式可以用 bind 方法改写。
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
上面代码的含义就是,将 Array.prototype.slice 变成 Function.prototype.call 方法所在的对象,调用时就变成了 Array.prototype.slice.call。类似的写法还可以用于其他数组方法。
var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);
var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]
pop(a)
a // [1, 2, 3]
如果再进一步,将 Function.prototype.call 方法绑定到 Function.prototype.bind 对象,就意味着 bind 的调用形式也可以被改写。
function f() {
console.log(this.v);
}
var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123
上面代码的含义就是,将 Function.prototype.bind 方法绑定在 Function.prototype.call 上面,所以 bind 方法就可以直接使用,不需要在函数实例上使用。
手写bind
// 1. 需求:手写bind => bind位置(挂在那里) => Function.prototype
Function.prototype.newBind = function() {
// 2. bind是什么?
const _this = this;
const args = Array.prototype.slice.call(arguments);
// args特点,第一项是新的this,第二项~最后一项函数传参
const newThis = args.shift();
// a. 返回一个函数
return function() {
// b. 返回原函数执行结果 c. 传参不变
return _this.apply(newThis, args);
}
}
手写apply
Function.prototype.newApply = function(context) {
// 边缘检测
// 函数检测
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 参数检测
context = context || window;
// 挂载执行函数
context.fn = this;
// 执行执行函数
let result = arguments[1]
? context.fn(...arguments[1])
: context.fn();
// 销毁临时挂载
delete context.fn;
return result;
}
this 的指向哪几种 ?
总结起来,this 的指向规律有如下几条: