探索ES6类构造器中创建原型同名变量的原理

446 阅读3分钟

2020/03/25

我开始学习react

当看到react在创建组件时this绑定问题的时候起了疑问: ES6中class中所写的方法默认时在原型对象上的 当时我使用ES5的思维:

function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
return this.name;
};

在ES6中使用class

class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
//1. sayName = function(){} 是创建实例方法
//2. static sayName(){} 是创建静态方法 Person.sayName
//3. sayName(){} 是创建原型方法
sayName(){
return this.name;
}
}

我在react创建组件绑定事件的过程中看到了如下代码,满脸问号ES6的class不只是一个语法糖么?

class Person{
state = {};//创建state
constructor(props){
this.handleClick = this.handleClick.bind(this);//这是啥?我有这属性?
}
handleClick(e){
//doSomething
}
render(){/* return (<div>...</div>) */}
}

What? && Why?

this.handleClick = this.handleClick.bind(this);

既然问题迎面而来,那么接下来开始分析之旅吧?

1.通过Chrome的F12我了解到是原型上和实例上都是有一个同名属性

2.原型上的属性是普通的方法,实例(obj)上的属性是此方法调用Function.prototype.bind(obj)返回的bound

那么什么是bound呢? 如果你对bound了解了,那么可以尝试看一下github上我写了一个bind的实现原理

//New的实现
function New(constructor, ...args) {
let o = {};
constructor = typeof constructor === 'function' ? constructor : function() {};
o.__proto__ = constructor.prototype; //这一步骤决定最后打印出来的o这个对象的类型
let returnVal = constructor.Apply(o, args); //至于打印出来的对象标识 取决于 原型上的constructor属性指向的函数名字,而这个name属性是无法改变的
//这里的returnVal会一直被引用,这里只是一个概念,应该通过参数传递,这里使用之后赋值null
return returnVal && typeof returnVal === 'object'? returnVal : o;
}

//Call的实现
Function.prototype.Call = function(that) {
//这里需要一个不会重复的键值作为{key:value}中的key
const fn = Symbol('fn'); //如果不是用ES6中的Symbol 自己创建一个(Math.random() + new Date().getTime()).toString(32).slice(8)
//这里将除that外的参数列表转换为数组,以为arguments中有Symbol.iterator所以可以使用of和...这两个操作
//其实还可以使用 Array.prototype.slice.call(arguments).slice(1);
const args = [...arguments].slice(1);
//如果that 为 undefined/null 这时this指向window
that = that || window;
//将独一无二的key 配上 value:Call的调用函数
that[fn] = this;
//执行这个函数,并将结果在最后返回
const res = that[fn](...args);
//不要忘记删除这个属性
delete that[fn];
return res;
};
//Apply的实现
Function.prototype.Apply = function(that) {
let fn = Symbol('fn');
let args = arguments[1];
that = that || window; //undefined / null that ->window
that[fn] = this;
let res = that[fn](...args);
delete that[fn];
return res;
};
//Bind的实现依赖Apply/Call
Function.prototype.Bind = function(that) {
that = that && (typeof that === 'object' || typeof that === 'function') ? that : window;
let args = Array.prototype.slice.call(arguments).slice(1);
let self = this;

let bound = function() {
//判断是否通过new调用这个函数
//如果是 new bound(),构造函数中的this被绑定到了一个被指定__proto__ == <constructor>.prototype
//这时候使用 this instanceof pure
return self.Apply(Instanceof(this, bound) ? this : that, args.concat(...arguments));
}
//箭头函数的prototype是undefined
//这里吧bound.prototype 改变到了调用bind函数的self上
if (self.prototype) {
bound.prototype = self.prototype;
}
return bound;
}

最后通过babel将ES6转成ES5解决了我的疑问

这里用到的知识点

1.闭包Closure

2.变量提升function var