1 the introduction

Javascript this is a troublesome topic, and this close reading article has led to a point of view, avoid using this. Let’s see if that makes sense.

This intensive reading article is classes-complexity-and-functional-programming

2 Content Summary

This is a complex design in javascript language. Compared with pure objects and functions, this brings the following problems:


const person = new Person('Jane Doe')
const getGreeting = person.getGreeting
// later...
getGreeting() // Uncaught TypeError: Cannot read property 'greeting' of undefined at getGreeting
Copy the code

Even react uses bind, which allows callbacks to access setState and other functions.

This is also bad for testing. If you use pure functions, you can test by taking in and out arguments without having to pre-initialize the environment.

So we can avoid using this and look at the following example:


function setName(person, strName) { return Object.assign({}, person, {name: strName}) } // bonus function! function setGreeting(person, newGreeting) { return Object.assign({}, person, {greeting: newGreeting}) } function getName(person) { return getPrefixedName('Name', person.name) } function getPrefixedName(prefix, name) { return `${prefix}: ${name}` } function getGreetingCallback(person) { const {greeting, name} = person return (subject) => `${greeting} ${subject}, I'm ${name}` } const person = {greeting: 'Hey there! ', name: 'Jane Doe'} const person2 = setName(person, 'Sarah Doe') const person3 = setGreeting(person2, 'Hello') getName(person3) // Name: Sarah Doe getGreetingCallback(person3)('Jeff') // Hello Jeff, I'm Sarah DoeCopy the code

Thus the Person instance is a pure object, with no methods mounted to the prototype chain, and is straightforward.

Alternatively, you can place the attribute on the parent function to avoid using this and avoid the hidden danger caused by this loss:


function getPerson(initialName) { let name = initialName const person = { setName(strName) { name = strName }, greeting: 'Hey there! ', getName() { return getPrefixedName('Name') }, getGreetingCallback() { const {greeting} = person return (subject) => `${greeting} ${subject}, I'm ${name}` }, } function getPrefixedName(prefix) { return `${prefix}: ${name}` } return person }Copy the code

The above code does not use this, and it does not cause problems.

3 intensive reading

In my opinion, the confusion caused by class is mainly caused by this. This is mainly because the member function is attached to prototype. Although multiple instances share references, the hidden danger is the uncertainty of this. There are many ways this can be lost, such as implicit binding alias loss implicit binding callback loss implicit binding explicit binding new binding arrow function changes this scope and so on.

Since the prototype object relies on this, if this is lost, the prototype chain will not be accessible, which will not only raise an error, but also need to be aware of the scope of this when writing code. Therefore, the author has the following solutions:


function getPerson(initialName) {
  let name = initialName
  const person = {
    setName(strName) {
      name = strName
    }
  }
  return person
}
Copy the code

The resulting Person object is not only a simple object, but because this is not called, there is no case of this being lost.

I don’t agree with that. Of course, the method is fine, the code logic is correct, and the prototype chain access problem of this is solved, but this does not prevent the use of this. Let’s look at the following code:


class Person {
  setName = (name) => {
    this.name = name
  }
}

const person = new Person()
const setName = person.setName
setName("Jane Doe")
console.log(person)
Copy the code

So we’re using this, and we’re creating alias lost-implicit binding, but the reason this is still accessible is because instead of putting the setName method on the prototype chain, we’re putting it on every instance, so whatever we lose this, we’re just missing the method on the prototype chain, But this looks for methods on its object first anyway, so as long as the methods aren’t on the prototype chain, you don’t have to worry about losing them.

As for the problem of saving multiple instances on the prototype chain, there is no way to avoid the function. If you want to get rid of this, the class approach can also solve the problem.

3.1 Loss of this

3.1.1 Default Binding

In strict mode, the default binding is different from that in non-strict mode. In non-strict mode, this is bound to the parent scope, while use Strict is not bound to window.


function foo(){
  console.log(this.count) // 1
  console.log(foo.count) // 2
}
var count = 1
foo.count = 2
foo()
Copy the code

function foo(){
  "use strict"
  console.log(this.count) // TypeError: count undefined
}
var count = 1
foo()
Copy the code

3.1.2 Implicit binding

When a function is called with an object reference, this is bound to the object to which it is attached.


function foo(){
  console.log(this.count) // 2
}
var obj = {
  count: 2,
  foo: foo
}
obj.foo()
Copy the code

3.1.3 Alias Loss Implicit binding

When a function reference is called, this depends on the caller’s environment.


function foo(){ console.log(this.count) // 1 } var count = 1 var obj = { count: 2, foo: Foo} var bar = obj.fooCopy the code

3.1.4 Callback lost implicit binding

This is similar to the react default, where functions are passed to child components and this is lost when called.


function foo(){
  console.log(this.count) // 1
}
var count = 1
var obj = {
  count: 2,
  foo: foo
}
setTimeout(obj.foo)
Copy the code

3.2 Fix for this binding

3.2.1 Bind Explicit binding

Use bind to display bindings.


function foo(){
  console.log(this.count) // 1
}
var obj = {
  count: 1
}
foo.call(obj)

var bar = foo.bind(obj)
bar()
Copy the code

3.2.2 es6 binding

This is similar to using the arrow function to create a member variable, creating an anonymous function that is not mounted to the prototype chain, so this is not lost.


function foo(){
  setTimeout(() => {
    console.log(this.count) // 2
  })
}
var obj = {
  count: 2
}
foo.call(obj)
Copy the code

3.2.3 函数 bind

In addition, we can specify the scope of the callback function so that this points to the correct prototype chain.


function foo(){
  setTimeout(function() {
    console.log(this.count) // 2
  }.bind(this))
}
var obj = {
  count: 2
}
foo.call(obj)
Copy the code

Block-level scopes exist even under if blocks, thanks to the extensive use of let const syntax:


if (true) {
  var a = 1
  let b = 2
  const c = 3
}
console.log(a) // 1
console.log(b) // ReferenceError
console.log(c) // ReferenceError
Copy the code

4 summarizes

We should not avoid using this because the binding is missing, causing unexpected errors. The root cause of this is javascript’s prototype chain mechanism. This mechanism is very good, saving objects on the prototype chain facilitates sharing among multiple instances, but it inevitably brings the prototype chain search process. If the object’s running environment changes, its prototype chain will also change, and the benefits of shared memory cannot be enjoyed. We have two options: One is to use bind to find the prototype chain, and the other is to be lazy and put the function on the object instead of the prototype chain.

React automatic bind was done at the framework level before it was scrapped because it was too black box. If the details of this are hidden for developers and the framework level is automatically bound, it seems convenient for developers, but the expectation of developers on this is excessively raised. Once the dark magic is removed, many developers will not adapt to the confusion caused by this. Therefore, it is better to disclose this problem to developers at the beginning and use the decorator with automatic binding. Either manually bind(this) at the callback, or place the function directly on the object, will solve the problem.