JavaScript 中的this是什么?它到底做了什么?

题目来源及自己的思路

最近在学习JS面向对象以及原型模式的过程中,有一些疑惑。
《JS中new操作符做了什么?》--Crushdada's shimo Notes 想到的--

function create(Con, ...args){   //Con即要new的构造函数  
  // 创建一个空的对象
  let  obj = Object.create(null);
  // 将空对象指向构造函数的原型链
  Object.setPrototypeOf(obj, Con.prototype);
  // obj绑定到构造函数上,便可以访问构造函数中的属性,即obj.Con(args)
  let result = Con.apply(obj, args);
  // 如果返回的result是一个对象则返回
  // 否则new方法失效,返回obj空对象
  return result instanceof Object ? result : obj;
}

在我整理的该篇中,new实现机制的最后一部(忽略使用instance容错那一步),是使用apply方法,然后就能通过obj对象就能够访问构造函数Con的属性和方法
在我以往的理解中,该方法是令构造函数的this指向空对象,那么--

函数内的this是函数的内置对象吗?这个指向是赋值吗?将构造函数的this对象赋给那个空对象?

然后百度的过程中,又看到一个JS类继承的例子:

var father = function() {
  this.age = 52;
  this.say = function() {
    alert('hello i am '+ this.name ' and i am '+this.age + 'years old');
  }
}
 
var child = function() {
  this.name = 'bill';
  father.call(this);
}
 
var man = new child();
man.say();

在最后一条语句man.say()的整个调用过程中,让我们把new关键字内部实现也分析一下来看,它最后会返回一个实例对象,然后,这个实例对象被赋给了man。

个人思考

当我们通过man.say()调用say方法,发生了什么?this是关键字吗?还是函数的内置对象?

很感谢边城的解答,下面总结、纠错和启发一下

  1. 我之前对apply有误解, 因为百度上都一直在强调改变this指向,让我太过于执着于它本身了,其实应该这样形容这个方法:在调用函数时,重定义this的指向
    2.其次,this在apply中的表现,可以当做引用
  2. 那么,以上代码理解起来就很简单了,执行new 语句的过程如下--
    首先,new关键字内部执行到-- let result = child.apply(man, null);
    会调用child函数,然后将其中的this直接换成man,就像这样↓
var child = function() {
  man.name = 'bill';
  father.call(man);
}

var man = new child();
man.say();

4.然后,执行到了call方法,同理,执行father方法并将其中的this换成man--

var father = function() {
  man.age = 52;
  man.say = function() {
    alert('xxx');
  }
}

5.然后,man对象已经存在那些属性和方法了,因此直接调用man.say()即可
可能这就是原型继承吧...
为了证实,我们可以打印一下:
image.png

未能解决的疑问

已经了解到this在apply方法使用中扮演的角色就像是引用,函数中的this.xx就像是预留给对象来apply似的,以此实现继承。
但是--
MDN对this有这样的两种描述--
1.“函数的 this 关键字”
2.它的值是--“当前执行上下文的一个属性”
以及:为什么我们能直接这样写--this.name = “Crushdada”;
这不是对象属性的声明方式吗?由此--
this本身到底是什么东西?是和new一样被封装的函数吗?

希望得到富有逻辑性的回答

阅读 3k
2 个回答
2021-05-06 补充

在 OOP (Object Oriented Programming) 中this 表示对象自身,虽然代码是写在“类”里,但是 this 要实例化的时候才会产生,表示的是当前实例对象本身 —— 这原本就没有什么逻辑,就是个规定,唯一有点逻辑的就是语义 this 就表示“自己”。

JavaScript 不是“经典”的 OOP,而是基于原型模式的 OOP,所以和“经典”OOP 有一点点区别。JS 的对象是通过构造函数来产生,而构造函数和普通函数本质没有区别,再加上 JS 对 this 的定义非常灵活(根据调用者、bindapply/call 等不同情况有不同的 this 指向,也就是“上下文的一个属性”)所以在 JS 中需要理解的不是 this 是什么,而是 this 引用什么(对 JS 的对象来说 ,都变量/属性都是引用),或者说得形象一点,“指向”什么。

它当然是指向一个对象,就如同你理解的在执行期将 this 指向了 man。既然是一个对象,它就可以像普通对象一样使用,比如直接通过赋值扩展属性:this.blabla = "lalalala"。至于 new,就如同你一开始给的那一段代码,它只是凭空产生了一个对象,然后将它和构造函数之间建立正确的联系。


2021-05-05 原答案

首先,现在写 JS 代码,尽量使用 ES6 以后的新语法,关于类、继承之类的处理,新语法简单明了还不容易出错,比如

class Super {
}

class Sub extends Super {
}

继承如果要用 ES5 的原型模式来实现,还真是需要花些周折(代码不写了,有兴趣可以搜)

我猜你没明白 Con.apply(obj, args); 这是在干什么。你可以查一下 Function.prototype.apply.call 的参考文档,他们的第一个参数都是强制给予函数调用的的 this 指向

假如这个 Con 是传入的下面这个函数

function SomeClass() {
    this.hello = "hi";
}

直接调用 SomeClass() ,在非 strict (严格)模式下,是给 global 对象(比如浏览器环境的 window)赋予一个 hello 属性。在 strict 模式下,thisundefined,这句话会报错。但是如果调用的时候强制给予 this 对象,比如

const obj = { mark: "just mark" };
SomeClass.apply(obj);
console.log(obj);    // 输出 { mark: 'just mark', hello: 'hi' }

ES5 中构造函数首先是一个函数,它在使用 new 调用的时候才称为构造函数。而 new 的时候正如你题中的代码那样,是先产生一个对象,再把它绑定给函数作为 this,执行函数中的代码。

Warning:这里是回答错误的地方,请忽略。错误原因查看 MDN 对 new 运算符的描述 中,new 关键字操作的第 4 步。


最后那句 return 是为了兼容函数中可能会返回一个其他对象的情况(构造函数也可以反回特定的对象,而不一定是 new 出来的对象),严格的说,我认为应该写成下面这样

return typeof result === "undefined" ? obj : result;

好了说了这么多,仍然只是“浅析”,建议你看看:JavaScript 的 this 指向问题深度解析

this更像是一个函数的参数,如果我们以这句话为标准的话,很容易就能模拟出this的行为
因为所有的函数的this都为函数的一个参数,所以其值除了被bind指定了的情况外,以调用方式密切相关,让我们暂时忽略原型链相关内容,用以上标准把代码改写成

var father = function (_this, age) {
  _this.age = age;
  _this.say = function (_this) {
    console.log(
      "hello i am " + _this.name + " and i am " + _this.age + "years old"
    );
  };
};

var child = function (_this, age, name) {
  _this.name = name;
  father(_this, age);
};

const man = {};
child(man, 28, "bill");
man.say(man);

由于this是作为参数传入的那么apply,call方法也就没了用武之地,那么我们来看看bind在这个标准下的表现是怎样的,我们可以轻松的实现bind


var print = (_this) => {
  console.log(_this.name + " " + _this.age);
};

/**
 *
 * @param {*} _bind_func_this bind的函数的this对象,没用用到
 * @param {*} func 要绑定this的函数
 * @param {*} _this 要和该函数绑定的this
 */
var bind = function (_bind_func_this, func, _this) {
  return function (__this, ...args) {
    return func(_this, ...args);
  };
};

const man1 = { name: "666", age: 666 };
var bindedPrint = bind(null, print, man1);

//绑定死this后,给bindedPrint赋值了一个新的this也是无效的
bindedPrint({ name: "777", age: 777 });

虽然代码很蹩脚,但是确实是在一定程度上模拟出了this的效果,而且还能更好的理解javascript中this的各种表现,假设有以下代码(在非严格模式下执行否则会报错),在javascript中就好像是把this当作了一个参数并根据具当前的调用对象自动传入this,bind了this的除外

var obj = {
  name: "obj_name",
  print() {
    console.log(this.name);
  }
};

var name = "window_name";

var obj2 = {
  name: "obj2_name"
};
var print = obj.print;
obj2.print = obj.print;

//相当于 obj.print(obj)
obj.print();

//相当于 obj2.pring(obj2)
obj2.print();

//等价于window.print也就是print(window)
print();

在javascript中可能很难体会到这一点让我们看看c++ primer里面是怎么说的
image.png

在面向对象语言里面说this可能还是不太有说服力,让我们看看用c语言怎么模拟出面向对象的效果,是不是感觉似曾相识,几乎和我们模拟this的javascript代码差不多


#include <stdio.h>
//C 语言没有类,但可以用结构体充当一个类
//与类不同,结构体只能定义变量,不能够定义函数,可以通过函数指针的方法来实现其功能 
//定义“类 ”的成员变量以及方法 
typedef struct Person{
    char name;
    int age;
    void (*EatFunction)(struct Person this, int num);
}Person; 
 
//定义函数功能 
void EatFunction(struct Person this, int num){
    printf("Test\n");
} 
 
//定义“类 ”的构造函数
//与面向对象不同,C语言的“类”的 构造函数不能放在“类”中,只能放在“类”外
//构造函数主要完成 变量的初始化,以及函数指针的赋值 
Person *NewPerson(Person *this){
    this->name = 'A';
    this->age = 18;
    this->EatFunction = EatFunction;
} 
 
//主函数调用 
int main(){
    Person person;
    NewPerson(&person);
    person.EatFunction(person,0);
    return 0;
}

结论:你的问题其实和用c语言怎么实现面向对象这个问题是重合的,this对象更像是一个函数的隐式参数,该值为在调用该函数时由请求该函数调用的对象,

参考资料:
c代码 https://blog.csdn.net/forever...

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏