一直以来,JavaScript 都没有类的概念。如果我们想要是创建一个类,通常是通过 构造函数 来实现。
// ES 5
// 添加实例属性
function Animal(name,type) {
this.name = name;
this.type = type;
this.owner = 'xiaoming'
}
// 添加实例共享的方法
Animal.prototype.sayName = function() {
console.log(this.name);
}
// 生成实例
const huahua = new Animal('huahua', 'cat')
huahua.sayName(); // 'hauhua'
我们在构造函数 Animal
中定义实例的属性,并通过 Animal.prototype
对象为实例添加公共的方法。
而 ES6 将这种写法进行了封装,实现了另一种写法—— Class —— 来构造类,与之前相比,写法更为简洁,也更符合类的概念。同时,React 也是通过 class 类来声明组件,所以,搞懂它,十分有必要。
如何用 Class 语法,实现上面的 Animal ?
class Animal {
constructor(name, type){
this.name = name;
this.type = type;
}
sayName() {
console.log(this.name)
}
}
const xiaoqiang = new Animal('xiaoqiang', 'insect')
xiaoqiang.sayName(); // 'xiaoqiang'
这种写法带来的简洁性,我们现在已经可以看到。接下来看一看,这种方式,与 ES5 的写法还有哪些不一致?
1. 构造函数
我们可以看到,在 Class 类中,存在一个 constructor
方法,我们在这个方法内做的事情,和使用 ES5 中的构造函数 Animal 一致,那这个方法,是不是就是类的构造函数呢?
Animal.constructor === xiaoqiang.__proto__.constructor; // false
Animal === xiaoqiang.__proto__.constructor; // true
事实证明,类 Animal 才是真正的构造函数,而 constructor, 只是 Animal 内部,实现构造的一个方法。
在 Class 中,我们通过 constructor 方法接收参数,并进行实例属性的定义。
如果实例本身的属性值不需要根据参数设置,我们可以直接在 Class 的顶部设置实例的属性。
class Toy{
owner = 'xiaoming';
friend = 'hh';
}
const some = new Toy();
some.owner; // 'xiaoming'
some.friend; // 'hh'
2. constructor
上面我们讲到, constructor
是 Class 类内部用来实现构造的一个方法。当我们执行 new Animal()
时,会执行 constructor
, 如果我们定义类时没有定义 constructor
时,Class 类会默认定义它。
就像上面的例子 Toy。
console.log(Toy.constructor); // Function() { [native code] }
3. 公共方法的定义
ES5 中,我们将公共的方法直接定义在原型对象上。比如在第一个例子中,我们通过 Animal.prototype.sayName = function() {}
的方式添加公共方法。
而在 Class 中,添加在 consturctor
方法之外函数,都是公共的方法,本质上,这些方法最终也还是添加到了实例的原型对象上,此时,或许就能更加理解 Class 是一个语法糖这种说法了。
class Animal {
// 通过 class 添加方法
say() {
console.log('111111')
}
run() {
console.log('222222')
}
}
// 通过原型对象添加方法
Animal.prototype.cry= function() {
consol.log('33333333')
}
// 无论通过哪种方式,方法都是添加在了原型对象上
Animal.prototype; // {cry: ƒ, constructor: ƒ, say: ƒ, run: ƒ}
4. this 的指向
了解 React 的人一定见过这样的代码:
class Root extends Component {
constructor(props) {
super(props);
this.state = {
name: 'wowo'
};
}
sayName() {
console.log(this.state.name)
}
render() {
return <div onClick={this.sayName.bind(this)}>lalalal </div>
}
}
我们在最后的 render 方法中返回的组件中绑定方法时,需要将 this 值重新绑定,否则,你会看到:
什么意思呢?就是 this 此时变成了 undefined
。为啥来?说来话长,我就长话短说吧!
首先,JavaScript 普通函数中 this 的值,是在这个函数被调用时才确定,而 react 组件实例是通过 JSX 渲染的,经过渲染之后,调用函数时 this 值早已经不是实例自己了,而是指向了全局环境。
第二,既然指向了全局,this 也应该是 window
啊,这是因为在 class 和模块内部 ,默认使用严格模式。
当然,解决办法也有很多,我就不重复了。
我们接着来看 Class 类中普通的函数,我们在调用的时候也要绑定 this 吗?
class Animal {
say() {
console.log('111111')
}
run() {
this.say();
console.log('222222')
}
}
const cat = new Animal();
cat.say(); // 11111 22222
const fun = cat.say;
fun(); // TypeError: Cannot read property 'say' of undefined
我们可以看到,当我们以 cat.say()
的方式调用时,this 值仍绑定在实例 cat 上,而当我们将这个方法赋值给新的变量 fun 后,this 值就丢失了,react 中 this 的丢失,本质上就像是这样。
当然,除了这些,还有私有变量和私有属性,这个两个问题在 Class 中仍旧没有得到解决。依旧要依靠别的方式解决。