# JavaScript手写系列

## 手写new

### new的过程

在我们手动实现一个new前，我们应该需要知道new的过程中发生了什么？

在new 构造函数的过程其实就是“类”实例化的过程，这个过程里干了件事。

1. 在内存中创建一个对象
2. 将此对象的`prototype` 指向构造函数的`prototype`
3. 将this指向此对象
4. 执行构造函数里的代码，也就是为this添加属性和方法
5. 如果返回的是一个非空对象，则返回此对象，否则返回this

### 实现myNew

好了，我们已经知道new 过程中干了什么，那么我们手动实现new也不再困难。

```js
function myNew (target,...args){
    if(!target){
        return;
    }
    const obj = {}
    obj.prototype = target.prototype
    // 执行target构造函数里的代码
    const result = target.call(obj,...args)
    return typeof result === 'object' ? result : obj
}
```

## 手写call

手动实现call乍一看好像挺难的，但是我们可以和new一样，先看call实现的原理，然后再手动实现一个

### call实现的原理

我们知道call的作用是改变this的指向并调用函数。

```js
[].toString() // ''
Object.prototype.toString.call([])  // [object Array]
```

那么我们改变this即可，this指向调用者，那么我们只要改变this的调用者就可以实现call的效果。

```js
// 函数的this指向调用者
function foo(){
    console.log(this) // window
}
let obj = {
    foo(){
        console.log(this) // obj
    }
}
```

并且call方法挂载在`Function.prototype`上的，所以我们也将函数挂载在`Function.prototype`，这样所有的函数都能使用(函数是Function的实例)4

### 实现myCall

```js
Function.prototype.myCall = function(target,...args){
    if (typeof this !== "function") {
    	throw new TypeError("not a function")
    }
    target.fn = this
    const result = target.fn(...args)
    delete target.fn
    return result
}
const obj = {name: '阿花'}
function foo(...args){
    console.log(this.name)
    console.log('收到参数',...args)
}
foo.myCall(obj,1,2)
// 阿花
// 收到参数 1 2
```

这样就是实现了myCall，其中里面最重要的两句代码我们来分析一下。`target.fn = this`，这条语句将`this`，也就是`Function`的实例函数(foo)挂载到obj上，**这样foo函数也就成了obj的一个方法**，然后`const result = target.fn(...args)`调用并且传参，这个时候调用foo时，foo的调用者是obj，因此this也指向obj，所以可以访问obj的属性`name`。

然后删除临时使用`fn`属性，不然会污染原来的对象，最后返回函数调用的结果即可。

### 优化

上面其实还有个问题，那就是将this挂载到对象`obj`的过程中使用了临时属性`fn`，最后为了避免污染又删除了，但是如果对象`obj`原本就有这个属性，那么这些操作就会删除原有的`fn`属性。

我们可以借助`symbol`来解决这个问题

```js
Function.prototype.myCall = function(target,...args){
    // this表示Function的实例，也就是说myCall必须是在函数上调用
    if (typeof this !== "function") {
    	throw new TypeError("not a function")
    }
    // 使用symbol作为属性，避免污染对象原有的属性
    const sym =  Symbol('临时属性')
    target[sym] = this
    const result = target[sym](...args)
    delete target[sym]
    return result
}
const obj = {name: '阿花'}
function foo(...args){
    console.log(this.name)
    console.log('收到参数',...args)
}
foo.myCall(obj,1,2)
// 阿花
// 收到参数 1 2
console.log(obj) // 查看是否有污染
// Object { name: "阿花" }
```

## 手写apply

实现了手写call后，手写apply就不是什么难事，直接上代码。

```js
Function.prototype.myApply = function(target,argArr){  // 第二个参数改为数组
    if (typeof this !== "function") {
    	throw new TypeError("not a function")
    }
    // 使用symbol作为属性，避免污染对象原有的属性
    const sym =  Symbol('临时属性')
    target[sym] = this
    const result = target[sym](...argArr)
    delete target[sym]
    return result
}
const obj = {name: '阿花'}
function foo(...args){
    console.log(this.name)
    console.log('收到参数',...args)
}
foo.myApply(obj,[1,2]) // 参数改为数组形式
// 阿花
// 收到参数 1 2
console.log(obj) // 查看是否有污染
// Object { name: "阿花" }
```

## 手写bind

`bind`和之前的`apply`、`call`不同的是，`bind`并不会立即执行函数，而是返回改变this后的函数。

乍一看好像很简单，我们只需要把之前调用的代码全部删掉，直接返回改变this后的函数即可。

```js
Function.prototype.myBind = function(target){  
    if (typeof this !== "function") {
    	throw new TypeError("not a function")
    }
    // 使用symbol作为属性，避免污染对象原有的属性
    const sym =  Symbol('临时属性')
    target[sym] = this
	// const result = target[sym](...argArr)
    // delete target[sym]
    return target[sym] 
}
const obj = {name: '阿花'}
function foo(...args){
    console.log(this.name)
    console.log('收到参数',...args)
}
var myFn = foo.myBind(obj) // 参数改为数组形式
myFn(1,2,3)
// <empty string>
// 1 2 3
console.log(obj) // 查看是否有污染
// Object { name: "阿花", Symbol("临时属性"): foo(args) }
```

这里有两个问题，一个是`console,log(this.name)`打印了`<empty string>`，说明改变this失败了，另一个就是临时属性`symbol`没有删掉，会造成污染。

这里其实不难解决，我们分析一下，为什么`this.name`没有生效，因为`var myFn = foo.myBind(obj)` 这条语句其实等于`myFn = obj[sym]`，然后我们最终调用的时候是在全局环境下调用的，也就是说调用者是全局对象window，而不是`obj`了。

因此我们只需要在外面再包裹一个函数，形成闭包即可，还可以在此函数里删除临时属性。

```js
Function.prototype.myBind = function(target){  
    if (typeof this !== "function") {
    	throw new TypeError("not a function")
    }
    // 使用symbol作为属性，避免污染对象原有的属性
    const sym =  Symbol('临时属性')
    target[sym] = this
	// const result = target[sym](...argArr)
    // delete target[sym]
    return function(...argArr){
     	const result = target[sym](...argArr)
    	delete target[sym]
    }
}
const obj = {name: '阿花'}
function foo(...args){
    console.log(this.name)
    console.log('收到参数',...args)
}
var myFn = foo.myBind(obj) // 参数改为数组形式
myFn(1,2,3)
// 阿花
// 1 2 3
console.log(obj) // 查看是否有污染
// Object { name: "阿花"}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/shen-ru-li-jie-javascript-xi-lie/javascript-shou-xie-xi-lie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
