写多了
react
是否是已经忘记了什么是原型链了,是否是已经忘记了那个纯真骚年的那份初心了,前端,写多了高大上的代码,是否是应该静下心来,好好学习下基础,下面慢慢回溯下几个常识点:前端
constructor
是每一个实例对象都会拥有的一个属性,并且这个属性的实在乎义在于一个指针,它指向了建立当前这个实例对象的类。react
function Person() {}
let p = new Person();
// ƒ Person() {}
console.log(p.constructor);
复制代码
控制台打印结果能够看出,p.constructor
指向的是 Person
对象,后面会详解 new
的过程。es6
constructor
的属性值是能够随时改变的,若是不赋值,那就默认指向了建立这个实例对象的类,若是赋值了,那就会指向所赋值。api
在通常开发中,咱们是否是不多用到这个属性啊,下面我就上点干货,来看看 Preact
源码里是怎么使用这个属性来解决业务场景的。数组
Preact
组件有两种建立方式,一种是利用类建立,继承 Preact.Component
父类或者不继承,拥有这个父类的 render
方法等属性,另外一种是经过 function
建立的无状态组件(PFC),下面我就来讲下 Preact
中是怎么使用 constructor
属性来处理的。babel
// 函数建立的无状态组件
const Foo = () => {
return <div>Foo</div>;
};
// 常见的容器组件建立方式
class App extends Preact.Component {
render() {
return (
<div> <Foo /> </div>
);
}
}
复制代码
// 上述组件通过babel后转码后的虚拟dom生成函数
Preact.createElement(
"div",
null,
React.createElement("p", null, "hahahaha"),
React.createElement(Foo, null)
);
// 该函数返回的是一个虚拟dom
var Foo = function Foo() {
return Preact.createElement("div", null, "Foo");
};
复制代码
if(typeof type === 'function'){
...
}
复制代码
上述代码中,Preact.createElement
方法中的第一个参数就是 type
,其中 Foo
就是 function
类型。dom
Foo
函数的两种形式if (Foo.prototype && Foo.prototype.render) {
}
复制代码
在代码中会判断 Foo
函数是否能访问 render
方法,首次渲染确定是没有的,全部,上述的判断会断定 false
,关键点来了,下面来看看若是处理的:函数
首先来看下 Preact.Component
代码的实现:oop
function Component(props, context) {
this.context = context;
this.props = props;
this.state = this.state || {};
// ...
}
Object.assign(Component.prototype, {
setState(state, callback) {},
forceUpdate(callback) {},
render() {}
});
复制代码
能够看出,若是是容器组件,继承了父类 Preact.Component
,就可以访问 render
方法,那么若是是无状态组件,怎样让这个组件拥有 render
方法:学习
let inst = new React.Component(props, context);
inst.constructor = Foo;
inst.render = function(props, state, context) {
return this.constructor(props, context);
};
复制代码
起初看这个寥寥几行代码,包含了很多细致的东西。
首先,它定义了 Preact.Component
这个类的实例对象 inst
,此时,这个 inst
的 constructor
默认指向 Preact.Component
这个类,接下来,给 inst
的 constructor
这个属性赋值了,改变指向函数 Foo
,最后给这个实例对象 inst
添加一个 render
方法,核心就在这个方法,这个方法执行了 this.constructor
,其实就是执行了 Foo
方法,而 Foo
方法最终返回的就是一个虚拟 dom。
如今就说通了,其实,无状态组件最终也会拥有一个 render
方法,触发后会返回一个虚拟 dom 或者是子组件。
let inst = new React.Component(props, context);
inst.render = function(props, state, context) {
return Foo(props, context);
};
复制代码
或许你能够说彻底能够不用 constructor
的也能实现啊,这就是 preact
的精妙之处了,在源码中会有一个数组队列 recyclerComponents
,这是专门用来回收销毁组件的,它的判断依据也是利用 constructor
属性:
if (recyclerComponents[i].constructor === Foo) {
// ...
}
复制代码
js 每一个对象都会拥有一个原型对象,即 prototype
属性。
function Person() {}
复制代码
Person
对象的原型对象就是 Person.prototype
对象:
Person.prototype
对象里有那些属性:
能够看出这个对象默认拥有两个原生属性 constructor
和 __proto__
。
constructor
上面说过了,全部的对象都会有,那么 __proto__
也是全部的对象都会有,它是一个内建属性,经过它能够访问到对象内部的 [[Prototype]]
,它的值能够是一个对象,也能够是 null
。
那么 __proto__
究竟是什么呢:
function Person() {}
let p1 = new Person();
复制代码
图中的两个红框能够看出,p1.__proto__
和 Person.prototype
指向了同一个对象。
// true
p1.__proto__ === Person.prototype;
复制代码
Person
对象能够从这个原型对象上继承其方法和属性,因此 Person
对象的实例也能访问原型对象的属性和方法,可是这些属性和方法不会挂载在这个实例对象自己上,而是原型对象的构造器的原型 prototype
属性上。
那么,Person.prototype
的 __proto__
又指向哪里呢?
看上图,能够看出 p1.__proto__.__proto__
指向了 Object.prototype
,p1.__proto__.__proto__.__proto__
最后指向了 null,由此能够看出了构建了一条原型链。
原型链的构建依赖于实例对象的 __proto__
,并非原对象的 prototype
ES6 设置获取原型的方法:
// 给p1原型设置属性
Object.setPrototypeOf(p1, { name: "zhangsan" });
// zhangsan
console.log(p1.name);
// {name: "zhangsan"}
Object.getPrototypeOf(p1);
复制代码
上图红框能够看出,Object.setPrototypeOf
其实就是新的语法糖,至关于给 P1.__proto__
这个属性赋值。
一个简单的案例:
经典的原型链图示:
举例,如何用原生 js 实现 Student
继承 Person
:
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.getInfo = function() {
return `${this.name} and ${this.age}`;
};
复制代码
实现继承,即要 Student
的实例可以访问 Person
的属性和方法,也要能访问 Person
原型上的方法 getName
。
首先来看下 es6 的继承:
class Person {
public name: string
constructor(name) {
this.name = name
}
getName(){
return this.name
}
}
class Student extends Person {
public age: number
constructor(name, age) {
super(name)
}
getAge() {
return this.age
}
}
let s = new Student('zhangsan', 20)
// zhangsan
s.name
// zhangsan
s.getName()
// 20
s.getAge()
复制代码
那么,用原生 js 怎么作呢,下面来一步一步的实现。
call
实现函数上下文继承function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
function Student(name, age) {
Person.call(this, name);
this.age = age;
}
Student.prototype.getInfo = function() {
return `${this.name} and ${this.age}`;
};
let s = Student("zhangsan", 20);
// zhangsan
s.name;
// error
s.getName();
复制代码
call
方法只是改变了 Person
中函数体内的 this
指向,并不能改变它的原型,因此没法访问 Person
方法的原型。
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
function Student(age) {
this.age = age;
}
Student.prototype.getInfo = function() {
return this.age;
};
Student.prototype = new Person("zhangsan");
let s = new Student(20);
// zhangsan
s.getName();
复制代码
Student.prototype
的值设置为父类的实例对象,这样就能很简单的实现 Student
的实例对象能访问到 Person
的原型,可是这也是也有问题的,与其说继承的是 Person
这个类,不如说是继承的是这个类的实例对象,就是 name = zhangsan
这个实例,和 oop
的思想有背。
/** * 继承函数的核心方法 */
function _extends(child, parent) {
// 定义一个中间函数,并设置它的 constructor
function __() {
this.constructor = child;
}
// 这个函数的原型指向父类的原型
__.prototype = parent.prototype;
// 子类的原型窒息那个这个中间函数的实例对象
child.prototype = new __();
}
复制代码
这个 _extends
方法,是实现的核心,两个知识点,一是定义了一个无参数的中间函数,并设置它的 constructor
;第二个就是对原型链的使用。
function Person(name) {
this.name = name;
this.getName1 = function() {
console.log("Person", this.name);
};
}
Person.prototype.getName = function() {
console.log("Person prototype", this.name);
};
// 这个方法必定要在定义子类原型以前调用
_extends(Student, Person);
function Student(name, age) {
this.age = age;
Person.call(this, name);
}
Student.prototype.getInfo = function() {
console.log("Student", this.age);
};
let s = new Student("zhangsan", 12);
// Person prototype zhangsan
s.getName();
// Student 12
s.getInfo();
复制代码
这样,就能简单是实现了继承,而且多重继承也是支持的
// 多重继承
_extends(MidStudent, Student);
function MidStudent(name, age, className) {
this.className = className;
Student.call(this, name, age);
}
let mids = new MidStudent("lisi", 16, "class1");
// Person prototype lisi
mids.getName();
// Student 16
mids.getInfo();
复制代码
有兴趣能够多多研究研究,网上有很多精品案例,这个是 js 的基础,确定比成天调用 api 有意思的多,收获的也会更多。
俗话说,没有女友的你能够new
一个对象,那么这个 new
一下,到底经历了什么呢。
let p1 = new Person();
复制代码
step1,让变量
p1
指向一个空对象
let p1 = {};
复制代码
step2, 让
p1
这个对象的__proto__
属性指向Person
对象的原型对象
p1.__proto__ = Person.prototype;
复制代码
step3, 让
p1
来执行Person
方法
Person.call(p1);
复制代码
如今看这个流程,是否是很简单,是否是有种豁然开朗的感受!
那要如何实现一个本身的 new
呢?
/** * Con 目标对象 * args 参数 */
function myNew(Con, ...args) {
// 建立一个空的对象
let obj = {};
// 连接到原型,obj 能够访问到构造函数原型中的属性
obj.__proto__ = Con.prototype;
// 绑定 this 实现继承,obj 能够访问到构造函数中的属性
let ret = Con.call(obj, ...args);
// 优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
}
复制代码
来测试下:
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
console.log(`your name is ${this.name}`);
};
let p2 = myNew(Person, "lisi");
// your name is lisi
p2.getName();
复制代码
完美实现了!