Introduction: Following the call and apply methods, this article will take you step by step to understand how to write these borrowed functions in a simple and logical way. Not difficult, please follow me down!

call

Fn. Call (context, a,b,c,d) is the result of mounting the function Fn to the context object, calling the function Fn from the context object and passing in parameters.

1. The first version of call
Function. The prototype. MyCall_1 = Function () {let context = the arguments [0] / / take context let arg = Array. The from (the arguments). Slice (1) // Create a unique key context[randomKey] = this // mount Fn to context const res =  context[randomKey](... Return res // return result}Copy the code

Have a try

Var obj = {name:1,age:2} var name = 'Leo', age = 18 function Fn(height) {console.log('name: ', this.name, 'age:', this.age,'height:',height)} Fn() // name: Leo age: 18 height: MyCall_1 (obj, '80cm') // Name: 1 age: 2 height: 80cmCopy the code

The first release is complete with its basic call implementation simulation. There are several problems with the first release

  1. myCall_1therandomKeyCan you make it truly unique?
  2. Each executionmyCall_1Will be incontextHang on an extra property
  3. aboutcontextThe ginseng problem
2. Version 2 call
Question 1: Function. The prototype. MyCall_2 = Function () {let context = the arguments [0] / / take context let arg = Array. The from (the arguments). Slice (1) Const randomKey = Symbol('call') // -------- problem 1-------> Declare a unique key value through Symbol context[randomKey] = this // mount Fn to Context const res = context[randomKey](... Fn. MyCall_2 (obj, '80cm') // name: 1 age: 2 height: 80cm obj // {name: 1, age: 2 height: 80cm obj // {name: 1, age: 1} 2, the Symbol (call) : ƒ}Copy the code

If you don’t understand, you can review Symbol

Question 2: Function. The prototype. MyCall_2 = Function () {let context = the arguments [0] / / take context let arg = Array. The from (the arguments). Slice (1) Const randomKey = Symbol('call') // -------- problem 1-------> Declare a unique key value through Symbol context[randomKey] = this // mount Fn to Context const res = context[randomKey](... Delete context[randomKey] // -------- Question 2-------> Delete the key-value pair from context after execution return res // return result} test it Fn.myCall_2(obj, '80cm') // name: 1 age: 2 height: 80cm obj // {name: 1, age: 2Copy the code

Question 3: How is native Call handled

Fn. Call (undefined, '80cm') // name: Fn. Call (undefined, '80cm') // name: Leo age: 18 height: 80cm 80cm Fn. Call (false, '80cm') // name: undefined constant age: undefined constant height: Fn. Call (true, '80cm') // name: undefined constant age: undefined constant height: 80cm Fn. Call ('', '80cm') // name: undefined constant age: undefined constant height: 80cm Fn. Call (1, '80cm') // name: undefined constant age: undefined constant height: 80cm 80cm Fn. Call (function aa(){}, '80cm') // name: aa age: undefined height: 80cm Fn. Call (this, '80cm') // name: Leo age: 18 height: 80cm Fn. Call ('80cm') // name: undefined age: undefined height: undefinedCopy the code

For question 3, what does myCall_2 do

Fn.myCall_2(null, '80cm') // Uncaught TypeError: Cannot set properties of null (setting 'Symbol(call)') Fn.myCall_2(undefined, '80cm') // Cannot set properties of undefined (setting 'Symbol(call)') Fn.myCall_2(false, '80cm') // Uncaught TypeError: context[randomKey] is not a function Fn.myCall_2(true, '80cm') // Uncaught TypeError: context[randomKey] is not a function Fn.myCall_2('', '80cm') // Uncaught TypeError: context[randomKey] is not a function Fn.myCall_2(1, '80cm') // Uncaught TypeError: Context [randomKey] is not a function Fn. MyCall_2 (function aa(){}, '80cm') // name: aa age: undefined height: Fn.myCall_2(this, '80cm') // Name: Leo Age: 18 Height: 80cm fn. myCall_2('80cm') // Uncaught TypeError: context[randomKey] is not a functionCopy the code

In contrast, see some questions about myCall_2

  1. whencontextfornull/undefined/thiswhencontextIs pointed to the global scope
  2. whencontextforfalse/true/''/1/whencontextAppears to be pointing to an unknown object (can be handled with wrapped objects)
  3. whencontextforfunctionThe phenomenon is equivalent to an object with a name key-value pair, but I think the function should be used as an object without doing anything.If you have any questions, welcome to discuss)
  4. whencontextBehaves like 2 when no value is passed (’80cm’ string as this)

Based on the above comparison, problem 3 was optimized

3. Version 3 Call (Final)
Question 3: Function. The prototype. MyCall_3 = Function () {let context = the arguments [0] / / take context let arg = Array. The from (the arguments). Slice (1) / / into / / the problem 3 refs if (context = = = null | | context = = = undefined) = {context window / / changes to the global} else {context = } const randomKey = Symbol('call') // -------- problem 1-------> Declare a unique key value through Symbol Context [randomKey] = this // Attach Fn to context const res = context[randomKey](... Delete context[randomKey] // -------- Question 2-------> Delete the key-value pair from context after execution return res // return result} test it Fn. MyCall_3 (null, '80cm') // name: Leo age: 18 height: 80cm 80cm Fn. MyCall_3 (false, '80cm') // name: undefined constant age: undefined constant height: 80cm Fn. MyCall_3 (true, '80cm') // name: undefined constant age: undefined constant height: 80cm Fn. MyCall_3 ('', '80cm') // name: undefined constant age: undefined constant height: 80cm Fn. MyCall_3 (1, '80cm') // name: undefined constant age: undefined constant height: MyCall_3 (function aa(){}, '80cm') // name: aa age: undefined height: MyCall_3 (this, '80cm') // name: Leo age: 18 Height: 80cm Fn. You can see that the myCall_3 test results remain consistent with native callCopy the code

For some questions

  1. aboutcallFunction this check problem: actuallyFunction.prototype.myCall_3This code will do the trickthisCheck the problem, you can think about
  2. Strict mode problem: in strict mode because it no longer points to the global scope:windowforundefinedsothis/null/undefinedErrors were reported in all cases, and the rest were consistent

At this point, the simulation implementation of Call is basically complete

apply

The only difference for Apply is the formatting difference, instead of passing in a sequence of arguments. It’s an array

1. The first edition of Apply
Function. The prototype. MyApply_1 = Function () {let context = the arguments [0] / / take context let arg = the arguments [1] / / into / / the problem 3 refs If (the context = = = null | | context = = = undefined) = {context window / / changes to the global} else {context = Object (context) / / } const randomKey = Symbol('apply') // -------- problem 1-------> Declare a unique key value through Symbol context[randomKey] = this // will Const res = context[randomKey](... Delete context[randomKey] // -------- Question 2-------> Delete the key-value pair from context after execution return res // return result} test it Fn. MyApply_1 (", ['80cm']) // name: undefined constant age: undefined constant height: 80cm Fn. MyApply_1 (obj, ['80cm']) // name: 1 age: 2 height: 80cm Fn. MyApply_1 (null, ['80cm']) // name: Leo age: 18 height: 80cm Fn. MyApply_1 (function aa(){}, ['80cm']) // name: aa age: undefined height: 80cm Fn. MyApply_1 (false, ['80cm']) // name: undefined constant age: undefined constant height: MyApply_1 (this, ['80cm']) // name: Leo age: 18 height: 80cmCopy the code

As you can see from the first version, the output of myApply_1 is pretty much the same as that of native Apply, and all you need to do is check the array. Now that call is implemented, you can use myCall_3 to help implement Apply

2. Version 2 apply
Function. The prototype. MyApply_2 = Function () {let context = the arguments [0] / / take context let arg = the arguments [1] / / remove the ginseng let type = Object. The prototype. ToString. MyCall_3 (arg) / / find out the type if (the slice (8, type. Length - 1)! == 'Array') throw new TypeError('CreateListFromArrayLike called on non-object'); / / question 3 if the context (= = = null | | context = = = undefined) = {context window / / changes to the global} else {context = Object (context) / / } const randomKey = Symbol('apply') // -------- problem 1-------> Declare a unique key value through Symbol context[randomKey] = this // will Const res = context[randomKey](... Delete context[randomKey] // -------- Question 2-------> Delete the key-value pair from context after execution return res // return result} test it Fn.myApply_2(obj, {}) // Uncaught TypeError: CreateListFromArrayLike called on non-object Fn. MyApply_2 (obj, ['80cm']) // name: 1 age: 2 height: 80cmCopy the code

In addition, I found that the native Apply array works fine. Let’s move on to the test

  • Fn.apply(obj, {}) // name: 1 age: 2 height: undefined
  • Fn.apply(obj, null) // name: 1 age: 2 height: undefined
  • Fn. Apply (obj, function(){}) // name: 1 age: 2 height: undefined
  • Fn. Apply (obj, undefined) // name: 1 age: 2 height: undefined
  • Fn.apply(obj) // name: 1 age: 2 height: undefined
  • Fn.apply(obj, true) // Uncaught TypeError: CreateListFromArrayLike called on non-object
  • Fn.apply(obj, ”) // Uncaught TypeError: CreateListFromArrayLike called on non-object
  • Fn.apply(obj, 1) // Uncaught TypeError: CreateListFromArrayLike called on non-object
  • Fn.apply(obj, {length:1,0:’80cm’}) // name: 1 age: 2 height: 80cm

According to the second version of myApply_2 code all the above tests reported errors, combined with the above comparison began to optimize

3. Version 3 apply (Final)
Function. The prototype. MyApply_3 = Function () {let context = the arguments [0] / / take context let arg = the arguments [1] / / remove the reference let typeAll = Object. The prototype. ToString. MyCall_3 (arg) / / find out the type const type = typeAll. Slice (8, TypeAll. Length - 1) / / String cutting the if (type = = = 'Boolen' | | type = = = 'String' | | type = = = 'Number') {/ / the above three types of value according to the test result error directly throw new TypeError('CreateListFromArrayLike called on non-object'); } else if (type === 'Null' || type === 'Undefined' ||type === 'Function' || (type === 'Object' && Object.keys(arg).length === 0)) { // {}/null/undefined/function arg = [] } else if (type === 'Object' && Object.keys(arg).length ! Arg = = 0) {/ / class Array = Array. The from (arg)} / / question 3 if the context (= = = null | | context = = = undefined) {context = Windows / / changes to the global } else {context = Object(context) // Become a wrapped Object processing} const randomKey = Symbol('apply') // -------- question 1-------> Declare a unique key value through Symbol context[randomKey] = this // Mount Fn to context const res = context[randomKey](... Delete context[randomKey] // -------- Question 2-------> Delete the key-value pair from context after execution return res // return result} test it Fn.myApply_3(obj, {}) // name: 1 age: 2 height: undefined fn. myApply_3(obj, null) // name: 1 age: 2 height: undefined fn. myApply_3(obj, null) Function (){}) // name: 1 age: 2 height: 2 Fn. MyApply_3 (obj) // Name: 1 age: 2 height: undefined constant Fn. MyApply_3 (obj) // name: 1 age: 2 height: 0 undefined Fn.myApply_3(obj, true) // Uncaught TypeError: CreateListFromArrayLike called on non-object Fn.myApply_3(obj, '') // Uncaught TypeError: CreateListFromArrayLike called on non-object Fn.myApply_3(obj, 1) // Uncaught TypeError: CreateListFromArrayLike called on non-object Fn. MyApply_3 (obj, {length:1,0:'80cm'}) // name :1 age: 2 height: At 80cm, you can see that the myApply_3 test results here remain consistent with native ApplyCopy the code

At this point, the simulation implementation of Apply is basically complete

The last

Original is not easy to hope that we support a lot, welcome to clap brick!

Related articles recommended:

  • 5 minutes quick handwriting implementation: bind
  • 5 minutes fast handwriting implementation: new