认识JS中的Class

更新日期: 2021-09-24阅读: 1.5k标签: class

1.JS中没有真正的类!

JavaScript 和面向类的语言不同,它并没有类来作为对象的抽象模式。JavaScript 中只有对象,而并没有真正的类。JS只是利用了函数的一种特殊特性——所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象,来模拟类的行为。

要注意的是,如果使用内置的 bind函数来生成一个硬绑定函数的话,该函数是没有 prototype 属性的,目标函数的 prototype 会代替硬绑定函数的 prototype。在这样的函数上使用 instanceof 或者new的话,相当于直接对目标函数使用 。
function Animal() {};
console.log(Animal.prototype);// {}

在JS中,new Animal()看起来像是实例化了Animal类,但是事实上并非如此。

const a = new Animal();
console.log(Object.getPrototypeOf(a) === Animal.prototype);// true

在面向类的语言中,类可以被复制(或者说实例化)多次。实例化一个类就意味着“把类的行为复制到物理对象中”,对于每一个新实例来说都会重复这个过程。

但是在 JS 中,并没有类似的复制机制。你不能创建一个类的多个实例,只能创建多个对象,它们的「prototype」 关联的是同一个对象。但是在默认情况下并不会进行复制,因此这些对象之间并不会完全失去联系,它们是互相关联的。new Animal() 会生成一个新对象(我们称之为 a ),这个新对象的内部链接 「prototype」 关联的是 Animal.prototype 对象。我们并没有初始化一个类,实际上我们并没有从“类”中复制任何行为到一个对象中,只是让两个对象互相关联。

2.JS中的构造函数是什么?

function Animal() {};
const a = new Animal();

在JS中并没有真正的类,但是在看到这两行代码时,我依然会觉得Animal是一个类。这是为什么呢?在我看来,一个原因在于出现了new操作符,而在面向类的语言中,需要使用new操作符。另一个原因是,在new Animal()中,Animal的调用方式特别像是调用方式很像实例化类时类构造函数的调用方式

但是实际上,Animal和你程序中的其他函数没有任何区别。函数本身并不是构造函数,只是当我们在普通的函数调用前面加上new 关键字之后,就会把这个函数调用变成一个“构造函数调用”(new会劫持所有普通函数并用构造对象的形式来调用它) 。

简单地说,在JS中,“构造函数”可以解释为使用new操作符调用的函数。但是,我们需要知道的是,JS中的函数并不是构造函数,只有使用new时,函数调用会变成构造函数调用

3.JS中的“面向类”

function Animal(name) {
    this.name = name;
}
Animal.prototype.sayName = function () {
    console.log(this.name);
};

const dog = new Animal('dog');
const cat = new Animal('cat');

dog.sayName(); // dog
cat.sayName(); // cat
console.log(dog.constructor); //  [Function: Animal]

this.name = name 通过this的隐式绑定给每个对象都添加了 name 属性,有点像类实例封装的数据值。

Animal.prototype.sayName = ... 会给 Animal.prototype 对象添加一个属性(函数)。在创建的过程中, dog 和 cat 的内 ),这个新对象的内部链接「prototype」 都会关联到 Animal.prototype 上。当 dog和 cat 中无法找到 sayName 时,它会在 Animal.prototype 上找到。

需要注意的是,dog.constructor指向了Animal函数,所以dog的constructor属性,似乎在代表着dog是由谁构造的。但是事实上,这仅仅是看起来如此,因为dog本身并没有constructor属性,constructor属性和sayName一样,同样是Animal.prototype 的属性,这个属性和dog(或者cat)之间没有什么联系。

对于Animal.prototype而言,constructor也仅仅是Animal函数在声明时所产生的一个默认属性而已(它是不可枚举的,但它是可以更改的),

Animal.prototype.constructor = 'animal';
console.log(dog.constructor); // 'animal'

当改变Animal.prototype的指向时,constructor属性的指向同样变得令人迷惑。这是因为fish上不存在constructor属性,所以查找的是Animal.prototype(即{})上的constructor属性,但是{}也没有constructor属性,所以会继续查找到Object.prototype 。这个对象有 constructor 属性,指向内置的 Object 函数。

Animal.prototype = {};
const fish = new Animal();
console.log(fish.constructor); // [Function: Object]

当然,我们可以手动指定constructor属性。

Animal.prototype = {};
Object.defineProperty(Animal.prototype, 'constructor', {
 enumerable: false, // 不可枚举
 writable: true,
 configurable: true,
 value: Animal, // 让 constructor 指向 Animal
});
const fish = new Animal();
console.log(fish.constructor); // [Function: Animal]

总而言之,constructor属性仅仅是一个普通的,可能会被更改的属性,dog.constructor这种引用是不可靠的。

4.继承

JS中原型风格的继承

function Animal(name) {
    this.name = name;
}
Animal.prototype.sayName = function () {
    console.log(this.name);
};

function Dog(name, color) {
    Animal.call(this, name);
    this.color = color;
}
// 创建了一个新的 Dog.prototype 对象并关联到 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
//Object.setPrototypeOf( Dog.prototype, Animal.prototype )
// 注意!现在 Dog.prototype.constructor 的指向已经变为了Animal
Dog.prototype.sayName = function () {
    console.log('重写sayName');
    //显式多态,调用Animal.prototype.sayName
    Animal.prototype.sayName.call(this);
};

const teddy = new Dog('泰迪', '棕色');
teddy.sayName(); // 重写sayName 泰迪

在声明Dog时,和所有的函数一样,Dog会有一个prototype属性指向默认对象(假设该对象名为originObj),但是originObj并不是我们想要的Foo.prototype。因此我们创建了一个新对象并把这个新对象关联到Foo.prototype,抛弃默认对象originObj。上面代码是通过Object.create实现的。当然也可以通过ES6的Object.setPrototypeOf实现,Object.setPrototypeOf( Dog.prototype, Animal.prototype ),这个函数是修改originObj,而不是放弃originObj,也因此通过Object.setPrototypeOf的话,Dog.prototype.constructor指向是没有发生变化的。

我们可以使用instanceof来检查teddy和Dog或者Animal等的关系。

console.log(teddy instanceof Animal);

instanceof左侧是一个普通的对象a,右侧是一个函数B,该操作符会检查B.prototype是否存在于a的「prototype」 链上。

如果想要检查两个普通对象之间的关系的话,可以使用isPrototypeOf

console.log(Animal.prototype.isPrototypeOf(teddy));

5.ES6中的class语法

你可能会认为 ES6 的 class 语法是向 JS 中引入了一种新的“类”机制,其实不是这样。 class 基本上只是 「prototype」 机制的一种语法糖。

class Animal {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}

class Dog extends Animal {
    constructor(name, color) {
        super(name);
        this.color = color;
    }
    sayName() {
        console.log('重写sayName');
        //相对多态
        super.sayName();
    }
}

const teddy = new Dog('泰迪', 'brown');
teddy.sayName();

除了语法更好看之外,ES6 还解决了什么问题呢?不再引用杂乱的 prototype 了。

Dog声 明 时 直 接“ 继 承 ” 了 Animal, 不 再 需 要 通 过 Object.create来 替换 prototype 对象,也不需要设置 __proto__ 或者 Object.setPrototypeOf。

可以通过 super来实现相对多态,这样任何方法都可以引用原型链上层的同名方法。不必使用显使多态的写法了,Animal.prototype.sayName.call(this);

class 字面语法不能声明属性(只能声明方法)。看起来这是一种限制,但是它会排除掉许多不好的情况,否则,原型链末端的“实例”可能会意外地获取其他地方的属性(这些属性隐式被所有“实例”所“共享”)。所以, class 语法实际上可以帮助你避免犯错

可以通过 extends 很自然地扩展对象(子)类型,甚至是内置的对象(子)类型,比如Array 或 RegExp 。没有 class ..extends 语法时,想实现这一点是非常困难的。

需要注意的是,super和this不同,super不是动态绑定的。下面的testObj.sayName中的super并非指向了它当前的「prototype」 对象testParen,而是指向了Animal。

const testObj = {
 name: 'test',
 sayName: Dog.prototype.sayName,
};
const testParen = {
 sayName() {
     console.log('testParen');
 },
};
Object.setPrototypeOf(testObj, testParen);
testObj.sayName();//重写sayName test

链接: https://fly63.com/article/detial/10684

ES6 class创建对象和传统方法生成对象的区别

JS语言传统创建对象的方法一般是通过构造函数,来定义生成的,下面是一个使用function生成的例子。ES5是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数

es6新特性之 class 基本用法

ES6 提供了更接近传统语言的写法,引入了 class(类)这个概念,作为对象的模板。通过class关键字,可以定义类

mixin配合class实现多继承的绝佳妙用

mixin一般翻译为“混入”、“混合”, 早期一般解释为:把一个对象的方法和属性拷贝到另一个对象上;Mixin是一种思想,用来实现代码高度可复用性,又可以用来解决多继承的问题,是一种非常灵活的设计模式

Class:向传统类模式转变的构造函数

JS基于原型的类,一直被转行前端的码僚们大呼惊奇,但接近传统模式使用class关键字定义的出现,ES6的class只是个语法糖,其定义生成的对象依然构造函数。不过为了与构造函数模式区分开,我们称其为类模式。

JS 总结之class

class 是 ES6 的新特性,可以用来定义一个类,实际上,class 只是一种语法糖,它是构造函数的另一种写法。用法和使用构造函数一样,通过 new 来生成对象实例

class 类 this指向的问题

ES6中的 class定义一个类, 其内部包含 constructor构造函数, 除了在构造函数显示的定义一些属性, 其余的默认都添加到这个类的原型对象上。以上代码 classProxy(prosen2) 返回的是包含一层拦截器的实例对象, 当读取 sayName这个函数的是和会出发 get拦截等操作。

classList介绍和原生JavaScript实现addClass、removeClass等

使用jQuery可以给元素很方便的添加class和删除class等操作,现在原生的JavaScript也可以实现这个方法了。使用classList可以方便的添加class、删除class、查询class等。elementClasses 是一个 DOMTokenList 表示 element 的类属性 。

Class 的私有属性与私有方法

proposal-class-fields与proposal-private-methods定义了 Class 的私有属性以及私有方法,这 2 个提案已经处于 Stage 3,这就意味着它们已经基本确定下来了,等待被加入到新的 ECMAScript 版本中。

ES6 static

ES6中新增了class这个语法糖。此class并非java中的class,ES6中的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。其中有static关键字。

vue.js 动态绑定class

vue 指令以 v- 前缀标示,数据绑定的指令 v-bind:属性名, 简写为 :属性名, 简单的数据绑定例子如下;vue 的分隔符默认是 {{ }}, 在分隔符里面的字符串会被认为是数据变量,可以通过 方式设置class

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!