一. 什么是this?
在面向对象的编程语言中,this指向当前的对象,表示对当前对象的一个引用,也就是一个指针,代表一个地址。
二. this带来的好处
// 没有this的情况:
var obj1 = {
name: "jack1",
eat: function () {
console.log(obj1.name);
}
}
var obj2 = {
name: "jack2",
eat: function () {
console.log(obj2.name);
}
}
obj1.eat(); //obj1
obj2.eat(); //obj2
// 有this
function eat() {
console.log(this.name);
}
var obj3 = {
name: "jack3",
eat
}
var obj4 = {
name: "jack4",
eat
}
obj3.eat(); //obj3
obj4.eat(); //obj4
三. this指向
this表示当前对象的引用,是调用时候决定的。在刚接触JavaScript的时候,曾经听过有人说this的指向是谁调用我,我就指向谁,也一度以为这句话是圣经,好友道理。直到遇到了call,apply,箭头函数等,这条圣经已经不适用了,不得不考虑重学this。本文考虑在浏览器的环境下,在node的环境全局变量有些差异。
this的指向分为几种情况:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
- 箭头函数 个人认为默认绑定是隐式绑定的一种。
四. 默认绑定
- 全局函数的直接调用
function foo() {
console.log(this); //window
}
foo();
- 函数内部调用
function foo1() {
console.log(this); //window
}
function foo2() {
console.log(this); //window
foo1();
}
foo2();
- 对象属性引用
var obj = {
foo: function() {
console.log(this); //window
}
}
var fn = obj.foo; //fn引用obj.foo的方法
fn();
默认绑定是隐式绑定原因?
浏览器在解析Javascript代码的时候会经过预解析的过程,在预解析中会创建全局对象(Global Object),Javascript最外层定义的函数都会挂载到全局对象下面,作为属性,window也是全局对象的一个属性, 但是window的值是this,也就是我们平时说浏览器的全局对象是window的原因,本质是window只是全局对象的一个引用。
function foo() {
console.log(this);
}
// 预解析,生成全局对象GO
// GO = {
// foo,
// window: this
// }
foo(); //foo独立调用实际找到的是GO.foo,也就是window.foo
window.foo(); //this结果与上面的结果相同都是指向window
五. 隐式绑定
隐式绑定是圣经中的一个片段,谁调用我,我指向谁。(遇到显示绑定又挂了。。。优先级不够)
- 方法作为对象的属性
var obj = {
name: "jack",
foo: function() {
console.log(this); //obj
}
}
obj.foo();
- 方法是另外一个对象的属性
var obj1 = {
foo: function() {
console.log(this);
}
}
var obj2 = {
name: "jack2",
foo: obj1.foo
}
obj2.foo(); //obj2
六. 显示绑定
- call
var obj = {
name: "jack"
}
function sum(a,b) {
console.log(a + b);
console.log(this);
}
sum.call(obj,1,2); //3 obj
- apply
var obj = {
name: "jack"
}
function sum(a,b) {
console.log(a + b);
console.log(this);
}
sum.apply(obj,[1,2]); //3 obj
call与apply的区别仅仅是传递参数的形式不同。 3. bind
var obj = {
name: "jack"
}
function sum(a,b) {
console.log(a + b);
console.log(this);
}
var sum2 = sum.bind(obj,1);
sum2(2); //3 obj
bind方法参数传递是一个参数收集的过程,也是函数柯里化的一种,后面有时间,会分享一起函数的柯里化。 bind方法应用在箭头函数上是没有效果的。
七. new绑定
通过new操作符调用函数的过程:
- 在内存中创建一个空对象。
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
- 构造函数内部的this会指向新创建出来的对象。
- 执行函数体的代码。
- 如果函数没有返回非空对象,会返回新创建出来的对象。
function Person(name,age) {
this.name = name;
this.age = age;
}
var p = new Person("jack",18);
console.log(p); //Person {name: 'jack', age: 18}
八. 箭头函数中的this
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
var name = "外部name";
var obj = {
name: "jack",
foo: () => {
console.log(this.name);
}
}
obj.foo();
九. this绑定优先级
- 显示绑定高于隐式绑定
var obj1 = {
name: "obj1",
foo: function() {
console.log(this.name);
}
}
var obj2 = {
name: "obj2"
}
obj1.foo.call(obj2); //obj2
- new高于隐式绑定
var obj = {
name: "jack",
foo: function(name) {
this.name = name;
console.log(this.name);
}
}
new obj.foo("foo"); //foo
- new绑定高于显示绑定
function foo(name) {
this.name = name;
console.log(this.name);
}
var obj = {
name: "jack"
}
var bar = foo.bind(obj);
new bar("foo"); //foo
十. 特殊绑定null,undefined
当传入null/undefined时, 自动将this绑定成全局对象
function foo() {
console.log(this);
}
foo.call(null); //window
foo.call(undefined); //window
十一. 自定义call
call方法是函数实例的方法,所以定义在Function构造函数的原型上面,后面会分享一期原型,原型链的相关知识。
Function.prototype.myCall = function(obj,...args) {
obj = obj || window;
let key = Symbol("key");
obj[key] = this; //这里的this指向调用myCall方法的函数,有点绕
let result = obj[key](...args);
delete obj[key];
return result;
}
function foo(age) {
console.log(this.name,age);
}
foo.myCall({name: "jack"},18);
十二. 自定义apply
call与apply仅仅是传递参数的区别,实现思路完全是一样的
Function.prototype.myApply = function(obj,args) {
obj = obj || window;
let key = Symbol("key");
obj[key] = this; //这里的this指向调用myApply方法的函数,有点绕
let result = obj[key](...args);
delete obj[key];
return result;
}
function foo(age) {
console.log(this.name,age);
}
foo.myApply({name: "jack"},[18]);
十三. 自定义bind
Function.prototype.myBind = function(obj,...args1) {
return function(...args2) {
let key = Symbol("key");
obj[key] = this;
// 参数收集的过程,函数柯里化。
let result = obj[key](...args1,args2);
delete obj[key];
return result;
}
}
function foo(name,age) {
console.log(this,name,age);
}
foo.bind({name:"abc"},"jack")(18);
十四. 自定义new
function myNew(fn,...args) {
let obj = {};
obj.__proto__ = fn.prototype;
let result = fn.call(obj,...args);
return typeof result === "object" ? result : obj;
}
function Person(name,age) {
this.name = name;
this.age = age;
}
let p = myNew(Person,"jack",18);
console.log(p);