深入浅出JS—11 ES6中类class和extends的使用

ES6中引入class语法糖来定义类,采用extends关键字来实现类的继承。相比于ES5中构造函数的方式在书写的简便性上有了质的提升。但是万变不离其宗,本文带你解密语法糖背后的实现原理,看完你会对class和extends有更深入的理解和认识

文章目录

  • class构造类
  • extends继承类
  • 深入知识
    • class的底层原理
    • extends继承底层原理

class构造类

类中由四种方法构成:

  • 构造方法:定义属性,解决入参问题
  • 实例方法:将被挂载到原型对象上
  • 访问器方法:拦截某个属性的get和set
  • 静态方法:直接挂载到类对象上
class Person {
	// 1 构造方法
	// 对应ES5中来调用new构造函数后,执行的5步操作
	constructor(name, age) {
		this.name = name;
		this.age = age;
		this._address = "上海市";
	}

	// 2 实例方法
	eat() {
		console.log(this.name + ' is eating');
	}

	// 3 访问器方法
	get address() {
		console.log("拦截访问操作");
		return this._address;
	}
	set address(value) {
		console.log("拦截赋值操作");
		this._address = value;
	}

	// 4 静态方法
	static randomPerson() {
		const names = ["sd", "fr", "re", "weee"];
    	const nameIdx = Math.floor(Math.random() * names.length);
   		const name = names[nameIdx];
   		const age = Math.floor(Math.random() * 100);
    	return new Person(name, age);
	}
}

创建实例对象

// 创建对象实例
const p = new Person('zs', 18);
console.log(p); // {name: 'zs', age: 18, _address: '上海市'};

// 调用实例方法
p.eat() // 'zs is eating'

// 测试访问器方法
// 注意address属性挂载到是Person原型对象上的
console.log(p.address) // '拦截访问操作'
p.address = '北京市'; // '拦截赋值操作'

// 测试静态方法
// 注意:静态方法的调用方式
const p2 = Person.randomPerson()


extends继承类

类继承涉及到关键字extends,下面演示创建子类Student继承父类Person,涉及到两个关键字extends和super

class Student extends Person {
	// 构造方法
	constructor(name, age, id) {
		// JS引擎在解析子类的时候要求:如果使用了继承extends,那么子类的构造方法中,使用this之前必须调用父类的构造方法
		super(name, age); 
		this.id = id;
	}

	// 实例方法
	// 和父类方法同名,属于方法重写
	eat() {
		// 借助super关键字来调用父类方法
		super.eat();
		console.log('Student' + this.name + ' is eating');
	}
	// 访问器方法 同样可以定义,不再赘述
	// 静态方法
 	static staticMethod() {
    	super.randomPerson(); // 只有在子类静态方法中才可以调用父类的静态方法,子类其他方法不可
    	console.log("student static method");
  }
}

深入知识

如果还想了解class和extends等语法糖下包裹的内核,提升自己的JS功底,增加对于类的理解,请沉下心继续看。

class的底层原理

class是ES6中提出的构造类的语法糖,一些低版本的浏览器不支持ES6语法,所以需要借助于babel等工具将其转译为ES5的构造函数。babel的在线转化平台:https://babeljs.io/

  • 上文Person类对应ES5中实现
"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) {
  // 将实例方法、访问器属性挂载到构造函数Person的原型对象上
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
  // 将静态方法添加到构造函数Person对象上
  if (staticProps) _defineProperties(Constructor, staticProps);
  // 设置构造函数Person的原型对象不可更改
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

// ⭐从这里读
var Person = /*#__PURE__*/ (function () {
  // 1 构造方法
  // 对应ES5中来调用new构造函数后,执行的5步操作
  function Person(name, age) {
    _classCallCheck(this, Person); // 检查Person是否会作为构造函数来调用

    this.name = name;
    this.age = age;
    this._address = "上海市";
  } 

  _createClass(
    Person,
    [
      {
        key: "eat",
        value: function eat() {
          console.log(this.name + " is eating");
        } // 2 实例方法
      },
      {
        key: "address",
        get: function get() {
          console.log("拦截访问操作");
          return this._address;
        },
        set: function set(value) {
          console.log("拦截赋值操作");
          this._address = value;
        } // 3 访问器方法
      }
    ],
    [
      {
        key: "randomPerson",
        value: function randomPerson() {
          var names = ["sd", "fr", "re", "weee"];
          var nameIdx = Math.floor(Math.random() * names.length);
          var name = names[nameIdx];
          var age = Math.floor(Math.random() * 100);
          return new Person(name, age);
        }// 4 静态方法
      }
    ]
  );

  return Person;
})();

tips

  • 立即执行函数好处:
    拥有独立的作用域,防止变量污染
  • /*#__PURE__*/标记的好处:
    表明该函数是一个纯函数,对外界变量不打扰、不依赖。这样webpack在打包时候,会执行tree-shaking,若纯函数没被用过,会被移除

extends继承底层原理

"use strict";

function _typeof(obj) {
  "@babel/helpers - typeof";
  return (
    (_typeof =
      "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
        ? function (obj) {
            return typeof obj;
          }
        : function (obj) {
            return obj &&
              "function" == typeof Symbol &&
              obj.constructor === Symbol &&
              obj !== Symbol.prototype
              ? "symbol"
              : typeof obj;
          }),
    _typeof(obj)
  );
}

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);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

function _get() {
  if (typeof Reflect !== "undefined" && Reflect.get) {
    _get = Reflect.get;
  } else {
    _get = function _get(target, property, receiver) {
      var base = _superPropBase(target, property);
      if (!base) return;
      var desc = Object.getOwnPropertyDescriptor(base, property);
      if (desc.get) {
        return desc.get.call(arguments.length < 3 ? target : receiver);
      }
      return desc.value;
    };
  }
  return _get.apply(this, arguments);
}

function _superPropBase(object, property) {
  while (!Object.prototype.hasOwnProperty.call(object, property)) {
    object = _getPrototypeOf(object);
    if (object === null) break;
  }
  return object;
}

// subClass: Student
// superClass: Parent
function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });
  Object.defineProperty(subClass, "prototype", { writable: false });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

// Student.__proto__ = Parent
function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  } else if (call !== void 0) {
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

function _isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {
    Boolean.prototype.valueOf.call(
      Reflect.construct(Boolean, [], function () {})
    );
    return true;
  } catch (e) {
    return false;
  }
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

// ⭐重要
var Student = /*#__PURE__*/ (function (_Person) {
  _inherits(Student, _Person); // 实现继承

  var _super = _createSuper(Student); // Person类不能直接调用,所以要创建一个

  // 构造方法
  function Student(name, age, id) {
    var _this;

    _classCallCheck(this, Student);

    // JS引擎在解析子类的时候要求:如果使用了继承extends,那么子类的构造方法中,使用this之前必须调用父类的构造方法
    _this = _super.call(this, name, age);
    _this.id = id;
    return _this;
  } // 实例方法
  // 和父类方法同名,属于方法重写

  _createClass(
    Student,
    [
      {
        key: "eat",
        value: function eat() {
          // 借助super关键字来调用父类方法
          _get(_getPrototypeOf(Student.prototype), "eat", this).call(this);

          console.log("Student" + this.name + " is eating");
        } // 访问器方法 同样可以定义,不再赘述
        // 静态方法
      }
    ],
    [
      {
        key: "staticMethod",
        value: function staticMethod() {
          _get(_getPrototypeOf(Student), "randomPerson", this).call(this); // 只有在子类静态方法中才可以调用父类的静态方法,子类其他方法不可

          console.log("student static method");
        }
      }
    ]
  );

  return Student;
})(Person);

你可能感兴趣的:(深入理解JavaScript,javascript,前端,面试,js)