this
: handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
目标:在React组件内的.this()
绑定过程中,我想完全理解所进行的引用以及计算机所采用的精确的逐步过程。
描述:我有一些代码(在下面列出),计算机内的代码通过行handleChange
绑定了this.handleChange = this.handleChange.bind(this);
输入处理程序。有一个父组件MyApp
,它具有一个子组件GetInput
和另一个子组件RenderInput
。
问题:
问题1。我的困惑主要来自于认为.this()
自动引用最接近的“父”对象,并且通过.this()
进行绑定将因此将其重定向到写有.bind()
的最接近的父对象。在以下情况下,它似乎重定向到MyApp
组件。但是,MyApp
类是函数console.log(typeof MyApp) //expected: function
。因此,为什么.this()
在下面的代码中没有引用全局对象?
问题2。当调用handleChange处理程序时,计算机执行哪些逐步处理?是否是以下内容:
RenderInput
组件内的初始调用:<p>{this.props.input}</p>
RenderInput
父元素,它是GetInput
组件:<input value={this.props.input} onChange={this.props.handleChange}/></div>
onChange={this.props.handleChange}
GetInput
组件的父对象,即MyApp
组件,并读取:handleChange={this.handleChange}
(这是我最不确定的步骤).this()
绑定到的位置:this.handleChange = this.handleChange.bind(this);
MyApp
作为this
的绑定值handleChange
处理程序:handleChange(event) {this.setState({inputValue: event.target.value });}
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
render() {
return (
<div>
{
<GetInput
input={this.state.inputValue}
handleChange={this.handleChange}
/>
}
{
<RenderInput input={this.state.inputValue} />
}
</div>
);
}
}
class GetInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Get Input:</h3>
<input
value={this.props.input}
onChange={this.props.handleChange}/>
</div>
);
}
};
class RenderInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Input Render:</h3>
<p>{this.props.input}</p>
</div>
);
}
};
让我们从this
和.bind
的行为都不是React的事实开始。因此,为了简单起见,让我们暂时忘记React,只看一些原始的JS代码(不用担心!稍后我们将返回React)。
现在让我们从头开始,这是一个对象:
{
username: "KamiFightingSpirit"
}
似乎很简单,但是对象的值可以是任意值(数组,其他对象,函数等)。我们还添加一个函数:
{
username: "KamiFightingSpirit",
logUsername: function () {
console.log( this.username );
}
}
this
到底是什么? this
指的是执行上下文,您可能还听说过:
[
this
/执行上下文是函数调用之前的点之前的任何内容。
考虑到this
与范围不同,让我们快速检查一下。它是在执行期间计算的。
const soUser = {
username: "KamiFightingSpirit",
logUsername: function () {
console.log(this.username);
}
};
soUser.logUsername();
// -> KamiFightingSpirit
好的,在执行期间this
等于soUser
。
// Let's just borrow the method from soUser
const userNameLogger = soUser.logUsername;
const nestedObjects = {
username: "This property will not be logged", // neither "KamiFightingSpirit" will be logged
sampleUser: {
username: "Igor Bykov",
logUsername: userNameLogger
}
};
nestedObjects.sampleUser.logUsername();
// -> Igor Bykov
很好,它又能正常工作了。 this
等于函数调用之前的点之前的对象。在这种情况下,对象是nestedObjects.sampleUser
的值。
再次注意,执行上下文与作用域不同。如果点前的对象中缺少使用的属性,则不会检查父对象中是否存在。这是相同的示例,但是缺少username
:
const nestedObjects = {
username: "undefined will be logged",
sampleUser: {
logUsername: userNameLogger
}
};
nestedObjects.sampleUser.logUsername();
// -> undefined
我们在那儿。现在,我们如何以编程方式创建大量用户?
// this is called constructor function
function User(name) {
// const this = {}; <- implicitly when used with "new" keyword
this.name = name;
// return this; <- implicitly when used with "new" keyword
}
console.log( new User("LonelyKnight") );
// -> {name: "LonelyKnight"}
这里new
强制创建一个新对象(并因此执行内容)。
但是,以这种方式创建对象非常危险。如果在没有new
的情况下调用相同的函数,它将执行,但不会创建新对象,并且this
将被评估为window
对象。这样,我们将有效地将name
分配给window
。
由于上述原因和其他一些原因,在较新版本的JavaScript class
中引入了该代码。类的作用与构造函数完全相同(实际上,它们are更聪明,更好的构造函数)。
因此,以下示例与上一个示例非常相似。class User {
constructor(name) {
this.name = name;
}
}
我们快到了!现在说,我们也希望能够更改用户名。
class User {
constructor(name) {
this.name = name;
}
changeName(newName) {
this.name = newName;
}
}
let batman = new User("Bat");
console.log(batman.name); // -> Bat
batman.changeName("Batman!");
console.log(batman.name); // -> Batman!
很酷,它有效!请注意,我们没有使用过.bind
。在这种情况下没有必要,因为我们在类的实例上执行所有操作。
现在,让我们回到React。在React中,我们倾向于将
functions >>(not
实例)从父母传递给孩子。如前所述,类非常类似于智能构造函数。因此,首先让我们看看如果对每个组件使用构造函数而不是类,我们的代码将是什么样。[如果我们扔掉React添加的所有JSX和合成糖,执行的操作将类似于以下内容:function Child(f) {
// Random property
this.rand = "A";
f(); // -> Window
}
function User(name) {
this.name = name;
this.logThis = function(){
console.log(this);
}
this.render = function(){
return new Child(this.logThis);
}
}
// Somewhere in React internals (in overly-simplified constructor-based universe)
const U = new User(``);
U.render();
请注意,由于我们仅调用f()
,因此前面没有点,因此,没有执行f()
的上下文。在这种情况下(除非设置了严格模式,否则,将this
评估为全局对象,该对象在浏览器中为Window
。现在,让我们回到类并编写非常相似的内容:
// Child component
class Child {
constructor(f) {
setTimeout(
() => f("Superman"), // -> throws "Cannot set "name" of undefined"
100
);
}
}
// Parent component
class User {
constructor(name) {
this.name = name;
}
changeName(newName) {
this.name = newName;
}
render() {
return new Child(this.changeName);
}
}
// Somewhere in React internals (in overly-simplified universe)
const batman = new User("batman");
batman.render();
由于类use strict mode by default,在上面的示例中,在f()
之前什么都看不到,将this
评估为undefined,尝试将新属性分配给undefined,并抛出错误的错误消息。]因此,为了避免这种情况,我们需要使用
.bind
或类似函数,以确保始终在正确的上下文中执行它。
.bind
到底是做什么的? Some internal black magic。为了完全理解它,您可能需要深入研究JS编译器代码(通常使用C / C ++编写)。
但是,有一个更简单的选择。 MDN(是一个很棒的网站)offers you ready-to-use polyfills基本上表明了如何在香草JS中重写.bind
。如果仔细观察,您会注意到两个polyfill都只包装了对.apply
或.call
的调用。因此,有趣的部分实际上并未“公开”。
我想这是因为我们无法使用内部机制,所以内部JS可能无法忠实地复制内部C ++ / C魔术。
但是,如果我们至少要严重地复制.bind
功能,我们会发现.bind
并不那么复杂(至少在基本级别上如此),它的主要功能只是确保执行上下文始终保持不变。
这里是.customBind
的致命实现:
// Please, never use this code for anything practical
// unless you REALLY understand what you are doing.
// Implements customBind
Function.prototype.customBind = function(context, ...bindedArgs) {
// context => intended execution context
// bindedArgs => original .bind also accept those
// Saves function that should be binded into a variable
const fn = this;
// Returns a new function. Original .bind also does.
return (...args) => {
// Symbol is used to ensure that
// fn's key will not unintentionally
// re-writte something in the original
// object.
const fnSymbol = Symbol();
// Since we can't directly manipulate
// execution context (not doable in JS),
// neither we can just call "context.fn()" since
// .fn is not in context's prototype chain,
// the best thing we can do is to dinamically
// mock execution context, so, we'll be able to
// run our binded function, inside the mocked
// context.
const contextClone = {
...context,
// adds binded function into a
// clone of its intended execution
// context.
[fnSymbol]: fn,
};
// Executes binded function inside the exact clone
// of its intended execution context & saves returned
// value. We will return it to the callee
// later on.
const output = contextClone[fnSymbol](...bindedArgs, ...args);
// Deletes property, so, it'll not leak into
// the original object on update that we're
// going to perform.
delete contextClone[fnSymbol];
// The function that we've run on our clone, might
// possibly change something inside the object it
// operated upon. However, since the object it
// operated upon is just a mock that we've created,
// the original object will stay unchanged. In order
// to avoid such a situation, let's merge our possibly
// changed clone into the original object.
context = Object.assign(context, contextClone);
// Finally, let's return to the callee,
// the result returned by binded function.
return output;
};
};
// Let's test it works!
const soUser = {
name: `Kami`,
logName: function() {
console.log(`My name is ${this.name}`);
},
changeName: function(newName) {
this.name = newName;
},
};
// Let's just borrow these methods from soUser
const soUserOwnedLogger = soUser.logName.customBind(soUser);
const soUserNameChanger = soUser.changeName.customBind(
soUser,
"KamiFightingSpirit"
);
// Let's use borrowed methods into another object.
const outterSystemUser = {
name: `UU-B235`,
soUserLogger: soUserOwnedLogger,
soUserChange: soUserNameChanger,
};
soUserOwnedLogger();
outterSystemUser.soUserChange();
soUserOwnedLogger();
console.log(`"name" in soUuser: ${soUser.name}`);
console.log(`"name" in outterSystemUser: ${outterSystemUser.name}`);
希望有帮助!
this
: handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
是指父函数handleChange
,而handleChange
不具有setState
方法。扩展class MyApp extends React.Component
时,组件是什么?这是[继承]setState
的React.Component
...这就是为什么我们必须手动将其绑定到该类(这只是语法糖,在您指出的情况下,它是一个函数...)] >更深入:当您像这样创建构造函数时:
function Person(name, age) {
this.name = name;
this.age = age;
}
然后您使用new关键字调用该函数,如下所示:
const personOne = new Person('Bob', 26)
幕后发生的是关键字new
创建了一个空对象并将this
设置为其引用,这就是为什么在函数体中我们具有this.name = name
等...您可以将其视为这样:
const this = {}
this.name = 'Bob'
this.age = 26
[this
现在将是一个像{ name: 'Bob', age: 26 }
]的对象>作为旁注:在许多示例中,您仅会看到类似以下的箭头功能:
handleChange = (event) => {
this.setState({
inputValue: event.target.value
});
}
那是因为箭头函数没有自己的this
上下文...它将自动冒泡到父级,并且不需要绑定...
this
: handleChange(event) {
this.setState({
inputValue: event.target.value
});
}