话说,只有掌握了JS中this用法才算真正的跨过了JS的门槛
。
this是什么?
指向对象的this
想必大家都记得那句话“this 永远指向最后调用它的那个对象”
。这也就是隐式绑定的规则,函数被调用时先看一看点号左侧。
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
}
}
cat.getName(); // "kitty"
为了避免不必要的麻烦,这也是最常用的一种方式。但是树林大了,什么鸟都有
,我们也要去了解更多的用法,才能以不变应万变。
指向window的this
什么样子的this是会指向window呢?
我们对上面的例子进行一个小小的改动
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
}
}
var realName = cat.getName;
realName(); // undefined
当然,我们按照前面的口诀,查找调用这个函数的对象的时候,即这个函数被调用时点号的左侧,并未发现没有任何对象,对于此口诀已经不实用,我们已然找不到this是谁了?那这个时候this
指向那里呢?那就是window
,而在全局中并没有定义 name
属性,所以我们会得到:
undefined
。
如果我们规定使用了ES5
中的"use strict"
的严格模式,那么我们就会收到报错:
Uncaught TypeError: Cannot read property 'name' of undefined
使用ES6箭头函数时的this
为了避免ES5
中的this
的问题,ES6
引入了箭头函数,箭头函数的this指向的是定义时的对象,而非运行时。
看个例子:
var name = "miao";
var cat = {
name: "kitty",
getName: function() {
console.log(this.name);
},
getArrowName: () => {
console.log(this.name);
}
}
cat.getName(); // "kitty"
cat.getArrowName(); // "miao"
ES6
中的this
规则:箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined
使用new时的this
function getName(name) {
this.name = name;
}
var newName = new getName("kitty");
console.log(newName.name); // "kitty"
new的实现原理
- 新建一个对象
obj
obj
对象中的__proto__
属性指向构造函数的原型,这样形成一个原型链。具体的构造函数和原型链内容可链接我的另一篇JS开发者应懂的33个概念系列5(下)--typeof 与 instanceof && 23--原型继承与原型链。- 利用
call
改变this
指向,显示绑定到obj
上(call
的实现原理将在下面讲述) - 如果
result
是对象类型并且不是null
,则返回result
new getName("kitty")
其实就做了这样的工作:
(function(){
var obj = {};
obj.__proto__ = getName.prototype;
var result = getName.call(obj,"kitty");
return (typeof result === 'object' && result) ? result : obj;
})()
使用call时的this
上面提到call
,它是可以改变this
的指向。使用和表现方式如下:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
cat.getAge(5, "miao"); // "kitty is 5 years old. miao"
var dog = {
name: "blank"
}
cat.getAge.call(dog, 2, "wangwang") // "blank is 2 years old. wangwang"
call的实现原理
划重点:面试题常考类型,小本本记一下:
Function.prototype.newCall = function(content) {
// 当call传入的对象是null的时候,或者其他一些类型的时候,函数会报错。
if(typeof content === 'object') {
content = content || window;
} else {
content = Object.create(null);
}
content.fn = this;
var keys = Array.from(arguments);
var arg = keys.slice(1);
content.fn(...arg);
delete content.fn;
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
cat.getAge.newCall(dog, 2, "wangwang");
使用apply时的this
和call
相比较,其实就是传参的类型不同而已:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "blank"
}
cat.getAge.apply(dog, [2, "wangwang"]) // "blank is 2 years old. wangwang"
apply的实现原理
Function.prototype.newApply = function(content, params) {
// 当call传入的对象是null的时候,或者其他一些类型的时候,函数会报错。
if(typeof content === 'object') {
content = content || window;
} else {
content = Object.create(null);
}
content.fn = this;
content.fn(...params);
delete content.fn;
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
cat.getAge.newApply(dog, [5, "wangwangwang"]);
使用bind时的this
bind
同样可以改变this
的指向,只不过其返回的是个函数:
var cat = {
name: "kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "blank"
}
cat.getAge.bind(dog, 2, "wangwang")(); // "blank is 2 years old. wangwang"
bind的实现原理
// 使用了ES6的扩展运算符
Function.prototype.newBind = function(content, ...params) {
var obj = this;
return function(...otherParams) {
obj.call(content, ...params, ...otherParams);
}
}
var cat = {
name: "Kitty",
getAge: function(age, voice) {
console.log(this.name + " is " + age + " years old. " + voice);
},
}
var dog = {
name: "Blank"
}
var newGetAge = cat.getAge.newBind(dog, 10, "wuwuwu")
newGetAge("wuwuwu");