ES6 class 该注意的细节
区分函数的多种的角色
// 函数/构造函数
function Fn(name) {
this.x = 100;
this.name = name;
}
// 作为原型,和「类/实例」有关
Fn.prototype.y = 200;
Fn.prototype.getX = function() {};
Fn.prototype.getName = function() {};
// 作为对象,和前面没有直接的关系
Fn.x = 1000;
Fn.getX = function() {};
Fn.getX(); //对象的成员访问
new Fn().getX(); //实例上的getX()方法
Fn(); //当做普通函数执行,返回undefined
上面是依据ES5的形式创造一个类
ES6中可以使用 class 关键字创建类
class Fn {}
Fn();
报错, Uncaught TypeError: Class constructor Fn cannot be invoked without 'new'
基于 class 声明的构造函数必须基于 new 执行,不允许当做普通函数执行
如何在es5中模拟这种判断?
如果是 new 执行,那么当前 this 一定是当前构造函数的实例,如果不是构造函数的实例,就说明不是 new 执行,就抛出错误
function Sum() {
if (!(this instanceof Sum)) throw new TypeError('constructor Sum cannot be invoked without new');
console.log('OK');
}
// Sum();//报错
new Sum();
构造函数
class Fn {
// 构造函数体
constructor(name) {
// this -> 创造的实例对象 「this.xxx=xxx设置的私有属性」
// this.x = 100;
this.name = name;
//如果返回对象,以返回的为主,否则返回实例对象
}
}
constructor 就是其构造函数体
私有属性简易写法
class Fn {
constructor(name) {
this.name = name;
}
x = 100; //等价于构造函数体中的“this.x=100”
}
但是构造函数中如果有了 this.x=xxx ,那么以构造函数中的最终赋值为准
原型上的内容设置
class不能在原型上设置属性,只能设置函数,为什么会有这个特性,一会再说
class Fn {
constructor() {}
y = 200; //等价于构造函数体中的“this.x=100”,并不是在原型上设置属性y
getX() {} //这里的方法是设置在原型上的
getName() {}
}
Fn.prototype.y = 200
用这种方法设置在原型上的函数是没有 prototype 的,类似于: obj={fn(){}}
如果想在原型上设置属性,只能用原来的方法写: Fn.prototype.y = 200 。
将类看做普通对象,设置私有属性
将类看做普通对象添加属性和方法,在 class 中,叫做静态私有属性和方法
class Fn {
constructor() {}
y = 200; //等价于构造函数体中的“this.x=100”,并不是在原型上设置属性y
getX() {} //这里的方法是设置在原型上的
getName() {}
static x = 1000
static getX() {}
}
以上就是如何通过es6创造一个类的过程
区分原型上和实例上的方法
class Fn {
x = 100;
getX = function() {}
getY = () => {}
getZ() {
console.log(this)
}
}
注意: x , getX , getY 都属于一类,最后在实例的私有属性上,没有不同。而 getZ 这种写法是声明在原型上的
const f = new Fn()
f.getZ()
Fn.prototype.getZ()
f.getZ.call(10)
所以 getZ 函数有可能 f.getZ() 执行,或者 Fn.prototype.getZ() 这样执行,或者 call 执行,其 this 的指向会根据不同情况改变
那么如何让其中的 this 不随便被改变而永远指向当前实例呢?答案是使用箭头函数的写法
class Fn {
getX1 = () => {
console.log(this)
}
}
const f = new Fn()
f.getX1()
f.getX1.call(10)
因为箭头函数没有 this ,其中的 this 是函数上下文中的 this ,这个 this 都是 Fn 的实例,因为在 new 的时候,都将构造函数内部 this 指向了返回的实例对象。
class Fn {
getX: function() {}
getY: () => {}
}
以上写法将报语法错误。不支持 : 写法。
在react,以上特性有这样的应用:
点击按钮,输出的是 undefined 。
分析如下:
onClick={this.handle}这里handle前面的this一定生成组件时候的实例,因为这个this是在render函数当中的,最后渲染组件的时候,一定是实例.render()的执行方式,所以这里的this一定是组件实例onClick={this.handle}最后会被转化为事件监听的形式,例如button.addEventListener('click',handle)。最后触发点击事件的时候。一定是以回调函数的形式触发,那么其中的handle(){console.log(this)}这个函数中的this在严格模式下是undefined,非严格模式下是window,所以上面这段代码输出undefined。
那我想让 handle 上 this 永远是实例,如何写呢?
使用箭头函数即可
handle = () => {
console.log(this)
}
此时 handle 并不是原型上的函数了,而是私有属性。并且因为其是箭头函数,所以 this是当前创建函数所在作用域上的this(当前创建函数的上下文的this),即外层的this,外层的this在new执行的时候,已经被绑定成了当前实例,所以此时this永远是当前实例.
还有一些方法可以规避这个问题,具体的可以看文档react-事件处理
使用Babel查看 class 到底做了什么
ES6 的
class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
我们打开Babel,将下面calss关键字语法糖代码转化为ES5,看看class语法糖到底是如何写的
class Fn {
constructor() {
this.name = 1
}
name = 2
static name = 3
}
结果为:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Fn = function Fn() {
_classCallCheck(this, Fn); //判断是否用new执行,如果没有用new ,那就报错
_defineProperty(this, "name", 2); //先执行name=2,给实例赋值为2,并且有重名,会覆盖赋值
this.name = 1; //在执行构造函数
};
_defineProperty(Fn, "name", 3); //给构造函数添加属性,相当于静态属性
所以,构造函数中的 this.name=1 后执行, name = 2 先执行
class Fn {
constructor() {
this.name = 1
}
name = 2
name = 4
static name = 3
getName() {
return this.name
}
getX() {
return this.x
}
static getY() {
return 'static getY'
}
}
转化后:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Fn = /*#__PURE__*/ function() {
function Fn() {
_classCallCheck(this, Fn);
_defineProperty(this, "name", 2);
_defineProperty(this, "name", 4);
this.name = 1;
}
_createClass(Fn, [{
key: "getName",
value: function getName() {
return this.name;
}
}, {
key: "getX",
value: function getX() {
return this.x;
}
}], [{
key: "getY",
value: function getY() {
return 'static getY';
}
}]);
return Fn;
}();
_defineProperty(Fn, "name", 3);
加入实例方法和静态方法后,发现其多了一个 _createClass 函数,其中的逻辑是按照传入的参数的顺序不同 ,只是简单的分别向原型对象和构造函数上添加方法,分别成为实例的共有方法和构造函数上的方法。
注意:
看了转换后就解释一个我思考过的疑问:为啥没法向原型上绑定静态的变量,而只能区分原型上的共有方法和构造函数上的静态方法呢?
因为在转换代码里 constructor(){this.name=1} 和直接写 name = 1 是一样的作用,最终被转换成了相同意义的代码,而 getName(){return this.name} 和 getName=()=>{return this.name} 则是不同逻辑,不同意义的代码
举例如下:
class Fn {
constructor() {
this.name = 1
}
name = 2
name = 3
static name = 4
getName() {
return this.name
}
static getStaticName() {
console.log(this)
return this.name
}
}
const x = new Fn()
x.getName() //返回1
x.getStaticName() //报错,x.getStaticName is not a function
Fn.getStaticName() //打印class Fn ,返回4
class Fn{
getA = ()=>{}
getB = function(){}
getC(){}
static getD = ()=>{}
static getE = function(){}
static getF(){}
}
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var Fn = /*#__PURE__*/function () {
function Fn() {
_classCallCheck(this, Fn);
_defineProperty(this, "getA", function () {});
_defineProperty(this, "getB", function () {});
}
_createClass(Fn, [{
key: "getC",
value: function getC() {}
}], [{
key: "getF",
value: function getF() {}
}]);
return Fn;
}();
_defineProperty(Fn, "getD", function () {});
_defineProperty(Fn, "getE", function () {});
将es5创造类的语法转化为es6的class语法
function Modal(x, y) {
this.x = x;
this.y = y;
}
Modal.prototype.z = 10
Modal.prototype.getX = function() {
console.log(this.x);
}
Modal.prototype.getY = function() {
console.log(this.y);
}
Modal.n = 200
Modal.setNumber = function(n) {
this.n = n;
}
let m = new Model(10, 20);
class Modal {
constructor(x, y) {
this.x = x;
this.y = y;
}
getX() {
console.log(this.x);
}
getY() {
console.log(this.y);
}
static n = 200;
static setNumber(n) {
this.n = n;
}
}
Modal.prototype.z = 10;
let m = new Model(10, 20);
一些关于面向对象的代码分析(加深理解)
function Fn() {
let a = 1;
this.a = a;
}
Fn.prototype.say = function() {
this.a = 2;
}
Fn.prototype = new Fn;
let f1 = new Fn;
Fn.prototype.b = function() {
this.a = 3;
};
console.log(f1.a);
console.log(f1.prototype);
console.log(f1.b);
console.log(f1.hasOwnProperty('b'));
console.log('b' in f1);
console.log(f1.constructor == Fn);
解析:
-
声明
Fn并,声明Fn.prototype.say
-
Fn.prototype = new Fn;分为两步
关系图有了之后,输出一目了然
小tips:检测某个属性是否为当前对象的属性
-
in:不论是私有还是公有属性(原型链),只要有结果就是true -
hasOwnProperty:检测是否为对象的私有属性,只要私有中没有这个属性,结果是false
检测当前属性是否为对象的公有属性
function hasPubProperty(obj, attr) {
return (attr in obj) && !obj.hasOwnProperty(attr);
}
这个方法局限性:如果私有中有这个属性,公有也有,此方法检测是不准确的
封装一个检测公有属性的方法(仅存在它的原型或者原型链上),
方法一:原理是根据原型链,一层一层往上找
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
// this -> obj
let self = this,
prototype = Object.getPrototypeOf(self); //获取原型
while (prototype) { //一直找到原型没有的时候停止循环
if (prototype.hasOwnProperty(attr)) { //如果在原型上有私有属性
return true;
}
prototype = Object.getPrototypeOf(prototype); //没有的话继续往上
}
return false;
};
方法二: in操作符检测的特点:先看自己私有中是否有,如果没有会默认按照原型链一层层查找
Object.prototype.hasPubProperty = function hasPubProperty(attr) {
let self = this,
prototype = Object.getPrototypeOf(self);
return attr in prototype;
};