JavaScript 新语法详解:Class 的私有属性与私有方法

译者按: 为什么偏要用#符号? 原文:JavaScript's new #private class fields译者:Fundebug本文采用意译,版权归原作者所有 proposal-class-fields与proposal-private-methods定义了 Class 的私有属性以及私有方法,这 2 个提案已经处于 Stage 3,这就意味着它们已经基本确定下来了,等待被加入到新的 ECMAScript 版本中。事实上,最新的 Chrome 已经支持了 Class 私有属性。 那么,对于 Class 的私有属性与私有方法,它们究竟是什么呢?它们是怎样工作的?为什么要使用#符号来定义呢? Class 的私有属性语法如下: class Point { #x; #y; constructor(x, y) { this.#x = x; this.#y = y; } equals(point) { return this.#x === point.#x && this.#y === point.#y; }}我们可以将其语法理解为 2 个部分: 定义 Class 私有属性引用 Class 私有属性定义 Class 私有属性私有属性与公共属性的定义方式几乎是一样的,只是需要在属性名称前面添加#符号: class Foo { publicFieldName = 1; #privateFieldName = 2;}定义私有属性的时候也可以不用赋值: ...

April 23, 2019 · 2 min · jiezi

ES5的用对象实现类的作用

// js实现类 ES5// 由于函数始对象 所以写法不是唯一// function Pf(){}更符合固有思想// 由于ES6添加了类,所以书写变得跟方便var Pf = function(name,age){ // 私有属性方法外面不能调用,只有对象方法可以操作,从而起到保护数据的作用 // 私有属性 var secret = ‘小秘密’ // 私有方法 function secretfn(){ console.log(‘私有方法’) console.log(“my secret is”+secret) } // 公共属性方法是每创建一个对象就会创建一个该属性或方法(耗费一定把内存) // 共有实例属性 this.name = name this.age = age //共有实例方法 this.say = function(){ console.log(“my name is”+this.name,“my age is”+this.age) console.log(‘可以操作私有属性与方法’) secretfn() }}// 静态方法Pf.f1 = function(){ console.log(‘我是静态方法,只能用类直接调用,实例对象不能调用’)}Pf.prototype = { constructor:Pf,// 这种添加原型方法需要重置制定对象。 // 原型链上的方法为公有方法,由类创建出来的对象会指向该原型,不会重新创建该方法,但是优先级没有对象方法高 // 其优点是节省内存 say:function(){ console.log(“原型上的say”) // 原型链上可以拿到共有属性,拿不到私有属性与方法 console.log(‘我也能拿到数据’+this.name) } }var a = new Pf(‘ss’,22)a.say()Pf.f1()运行直接node ...

April 14, 2019 · 1 min · jiezi

es6之js的类

起源JS 从创建之初就不支持类,也没有把类继承作为定义相似对象以及关联对象的主要方式,这让不少开发者感到困惑。而从 ES1 诞生之前直到ES5 时期,很多库都创建了一些工具,让 JS 显得貌似能支持类。尽管一些 JS 开发者强烈认为这门语言不需要类,但为处理类而创建的代码库如此之多,导致 ES6 最终引入了类。ES5 中的仿类结构JS 在 ES5 及更早版本中都不存在类。与类最接近的是:创建一个构造器,然后将方法指派到该构造器的原型上。这种方式通常被称为创建一个自定义类型。例如:function PersonType(name) {this.name = name;}PersonType.prototype.sayName = function() {console.log(this.name);};let person = new PersonType(“Nicholas”);person.sayName(); // 输出 “Nicholas"console.log(person instanceof PersonType); // trueconsole.log(person instanceof Object); // true此代码中的 PersonType 是一个构造器函数,并创建了单个属性 name 。 sayName() 方法被指派到原型上,因此在 PersonType 对象的所有实例上都共享了此方法。接下来,使用 new运算符创建了 PersonType 的一个新实例 person ,此对象会被认为是一个通过原型继承了PersonType 与 Object 的实例。这种基本模式在许多对类进行模拟的 JS 库中都存在,而这也是 ES6 类的出发点。es6基本的类的声明类声明以 class 关键字开始,其后是类的名称;剩余部分的语法看起来就像对象字面量中的方法简写,并且在方法之间不需要使用逗号。作为范例,此处有个简单的类声明:class PersonClass {// 等价于 PersonType 构造器constructor(name) {this.name = name;}// 等价于 PersonType.prototype.sayNamesayName() {console.log(this.name);}}let person = new PersonClass(“Nicholas”);person.sayName(); // 输出 “Nicholas"console.log(person instanceof PersonClass); // trueconsole.log(person instanceof Object); // trueconsole.log(typeof PersonClass); // “function"console.log(typeof PersonClass.prototype.sayName); // “function"es6中类关键字class本质是一种语法糖,而使用类实现的继承其本质上就是原型的继承.为何要使用类的语法类声明不会被提升,这与函数定义不同。类声明的行为与 let 相似,因此在程序的执行到达声明处之前,类会存在于暂时性死区内。类声明中的所有代码会自动运行在严格模式下,并且也无法退出严格模式。调用类构造器时不使用 new ,会抛出错误。试图在类的方法内部重写类名,会抛出错误。作为一级公民的类在编程中,能被当作值来使用的就称为一级公民( first-class citizen ),意味着它能作为参数传给函数、能作为函数返回值、能用来给变量赋值。 JS的函数就是一级公民(它们有时又被称为一级函数),此特性让 JS 独一无二ES6 延续了传统,让类同样成为一级公民。这就使得类可以被多种方式所使用。例如,它能作为参数传入函数:function createObject(classDef) {return new classDef();}let obj = createObject(class {sayHi() {console.log(“Hi!”);}});obj.sayHi(); // “Hi!使用派生类进行继承ES6 之前,实现自定义类型的继承是个繁琐的过程。严格的继承要求有多个步骤。例如,研究以下范例:function Rectangle(length, width) {this.length = length;this.width = width;}Rectangle.prototype.getArea = function() {return this.length * this.width;};function Square(length) {Rectangle.call(this, length, length);}Square.prototype = Object.create(Rectangle.prototype, {constructor: {value:Square,enumerable: true,writable: true,configurable: true}});var square = new Square(3);console.log(square.getArea()); // 9console.log(square instanceof Square); // trueconsole.log(square instanceof Rectangle); // trueSquare 继承了 Rectangle ,为此它必须使用 Rectangle.prototype 所创建的一个新对象来重写 Square.prototype ,并且还要调用 Rectangle.call() 方法。这些步骤常常会搞晕 JS的新手,并会成为有经验开发者出错的根源之一。类让继承工作变得更轻易,使用熟悉的 extends 关键字来指定当前类所需要继承的函数,即可。生成的类的原型会被自动调整,而你还能调用 super() 方法来访问基类的构造器。此处是与上个例子等价的 ES6 代码:class Rectangle {constructor(length, width) {this.length = length;this.width = width;}getArea() {return this.length * this.width;}}class Square extends Rectangle {constructor(length) {// 与 Rectangle.call(this, length, length) 相同super(length, length);}}var square = new Square(3);console.log(square.getArea()); // 9console.log(square instanceof Square); // trueconsole.log(square instanceof Rectangle); // true使用 super() 时需牢记以下几点:你只能在派生类中使用 super() 。若尝试在非派生的类(即:没有使用 extends关键字的类)或函数中使用它,就会抛出错误。在构造器中,你必须在访问 this 之前调用 super() 。由于 super() 负责初始化this ,因此试图先访问 this 自然就会造成错误。唯一能避免调用 super() 的办法,是从类构造器中返回一个对象。总结ES6 的类让 JS 中的继承变得更简单,因此对于你已从其他语言学习到的类知识,你无须将其丢弃。 ES6 的类起初是作为 ES5 传统继承模型的语法糖,但添加了许多特性来减少错误。 ...

April 1, 2019 · 2 min · jiezi

es6声明类实现继承

class声明一个animal类(对象):class Animal{ constructor(){//这个constructor方法内定义的方法和属性是实例化对象自己的,不共享;construstor外定义的方法和属性是所有实例对象(共享)可以调用的 this.type = ‘animal’ //this关键字代表Animal对象的实例对象 } says(say){ console.log(this.type+’ says ’ +say); }}let animal = new Animal();animal.says(‘hello’);//控制台输出‘animal says hello’这里声明一个Cat类,来继承Animal类的属性和方法class Cat extends Animal(){ constructor(){ super();//super关键字,用来指定父类的实例对象 this.type = ‘cat’; }} let cat = new Cat();cat.says(‘hello’);//输出‘cat says hello’

February 21, 2019 · 1 min · jiezi

C++类中的特殊成员函数

转载请注明文章出处:https://tlanyan.me/special-me…C++类中有几个特殊的非静态成员函数,当用户未定义这些函数时,编译器将给出默认实现。C++11前有四个特殊函数,C++11引入移动语义特性,增加了两个参数为右值的特殊函数。这六个函数分别是:默认构造函数默认构造函数指不需要参数就能初始化的构造函数。包含无参和所有参数有默认值两种类型的构造函数。复制构造函数复制构造函数指使用该类的对象作为参数的构造函数。可以有其他参数,但必须提供默认值。复制赋值运算符重载等号=,将该类的对象赋值给已定义对象。析构函数没啥可说的。移动构造函数C++11新增,该类的右值对象为参数的构造函数,其余同复制构造函数。移动复制运算符同复制赋值运算符,唯一不同是参数为右值。看定义容易迷糊,上代码就会很清晰:#include <iostream>#include <string>class Foo {public: std::string s; // 默认构造函数 Foo() { std::cout << “default constructor” << std::endl; } // 复制构造函数 Foo(const Foo& foo) { std::cout << “copy constructor” << std::endl; s = foo.s; } // 复制赋值运算符 Foo& operator=(const Foo& foo) { std::cout << “copy assignment operator” << std::endl; s = foo.s; return * this;} // 移动构造函数 Foo(Foo&& foo) { std::cout << “move constructor” << std::endl; s = std::move(foo.s); } // 移动赋值运算符 Foo& operator=(Foo&& foo) { std::cout << “move assignment operator” << std::endl; s = std::move(foo.s); return *this;}};int main() { Foo foo1; Foo foo2(foo1); foo1 = foo2; Foo foo3(std::move(foo1)); foo2 = std::move(foo3);}用g++或者clang编译,加上-fno-elide-constructors -std=c++0x选项。执行程序输出如下:default constructorcopy constructorcopy assignment operatormove constructormove assignment operator结果是我们预期的。需要注意的是Foo foo3 = foo1的形式会调用复制构造函数,不会调用复制赋值运算符。原因是Foo foo3 = xxx声明和定义一个新对象,而赋值是作用在已定义对象。移动赋值运算符同理。C++11新增了=default和=delete函数修饰符,提示编译器使用默认或者删除默认的特殊函数。需要注意的是这两个修饰符只能修饰上述特殊函数,用户可以用其对特殊函数进行裁剪。一个例子:struct Test { // 使用默认构造函数 Test() = default; // 删除复制赋值运算符 Test& operator=(const Test& test) = delete; // 使用默认析构函数 ~Test() = default;};参考https://en.cppreference.com/w…https://stackoverflow.com/que… ...

February 3, 2019 · 1 min · jiezi

es6类和继承的实现原理

在阅读文章之前,您至少需要对JavaScript原型继承有一定了解,如果觉得有所欠缺,可以先了解下我这篇文章:https://segmentfault.com/a/11…1.es6 class 使用javascript使用的是原型式继承,我们可以通过原型的特性实现类的继承,es6为我们提供了像面向对象继承一样的语法糖。class Parent { constructor(a){ this.filed1 = a; } filed2 = 2; func1 = function(){}}class Child extends Parent { constructor(a,b) { super(a); this.filed3 = b; } filed4 = 1; func2 = function(){}}下面我们借助babel来探究es6类和继承的实现原理。1.类的实现转换前:class Parent { constructor(a){ this.filed1 = a; } filed2 = 2; func1 = function(){}}转换后:function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Parent = function Parent(a) { classCallCheck(this, Parent); this.filed2 = 2; this.func1 = function () { }; this.filed1 = a;};可见class的底层依然是构造函数:1.调用_classCallCheck方法判断当前函数调用前是否有new关键字。构造函数执行前有new关键字,会在构造函数内部创建一个空对象,将构造函数的proptype指向这个空对象的_proto,并将this指向这个空对象。如上,_classCallCheck中:this instanceof Parent 返回true。若构造函数前面没有new则构造函数的proptype不会不出现在this的原型链上,返回false。2.将class内部的变量和函数赋给this。3.执行constuctor内部的逻辑。4.return this (构造函数默认在最后我们做了)。2.继承实现转换前:class Child extends Parent { constructor(a,b) { super(a); this.filed3 = b; } filed4 = 1; func2 = function(){}}转换后:我们先看Child内部的实现,再看内部调用的函数是怎么实现的:var Child = function (_Parent) { _inherits(Child, _Parent); function Child(a, b) { _classCallCheck(this, Child); var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, a)); _this.filed4 = 1; _this.func2 = function () {}; _this.filed3 = b; return _this; } return Child;}(Parent);1.调用_inherits函数继承父类的proptype。_inherits内部实现:function inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}(1) 校验父构造函数。(2) 典型的寄生继承:用父类构造函数的proptype创建一个空对象,并将这个对象指向子类构造函数的proptype。(3) 将父构造函数指向子构造函数的_proto(这步是做什么的不太明确,感觉没什么意义。)2.用一个闭包保存父类引用,在闭包内部做子类构造逻辑。3.new检查。4.用当前this调用父类构造函数。var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, a));这里的Child.proto || Object.getPrototypeOf(Child)实际上是父构造函数(_inherits最后的操作),然后通过call将其调用方改为当前this,并传递参数。(这里感觉可以直接用参数传过来的Parent)function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}校验this是否被初始化,super是否调用,并返回父类已经赋值完的this。5.将行子类class内部的变量和函数赋给this。6.执行子类constuctor内部的逻辑。可见,es6实际上是为我们提供了一个“组合寄生继承”的简单写法。3. supersuper代表父类构造函数。super.fun1() 等同于 Parent.fun1() 或 Parent.prototype.fun1()。super() 等同于Parent.prototype.construtor()当我们没有写子类构造函数时:var Child = function (_Parent) { _inherits(Child, _Parent); function Child() { _classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).apply(this, arguments)); } return Child;}(Parent);可见默认的构造函数中会主动调用父类构造函数,并默认把当前constructor传递的参数传给了父类。所以当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承。典型的例子就是Reatc的Component中,我们声明constructor后必须调用super(props),因为父类要在构造函数中对props做一些初始化操作。 ...

January 8, 2019 · 2 min · jiezi