刨析 JavaScript 模拟面向对象的内部机制

简介: 本文介绍 JavaScript 面向对象的内部机制

刨析 JavaScript 模拟面向对象的内部机制


作者李俊才( jcLee95 )

已入住阿里云博客

邮箱 :291148484@163.com
本文地址
- https://developer.aliyun.com/article/
- https://blog.csdn.net/qq_28550263/article/details/126446795

目 录


1. 构造对象的方法

2. 原型 与 原型链 的概念

3. JavaScript 继承 的内部机制


1. 构造对象的方法

1.1 通过字⾯量构造

letobj= {
name: "jcLee95", 
birthday: "07-30"};

1.2 通过Object构造器构造

letobj=newObject();
obj.name="jcLee95";
obj.birthday="07-30";

1.3 通过原型构造

letobj=Object.create({
name: "jcLee95",
birthday: "07-30"});

1.4 函数的构造调用

JavaScript 始终不是一个完全的面向对象编程语言,更多地像是在模拟面向对象地编程方式,原型链、构造调用等都是实现其面向对象地重要技术环节之一。构造调用 在形势上看,是使用 new语法糖的函数调用,本质上是JavaScript函数的一种调用方式,用于实现对基于类面向对象编程语言中类的实例化过程。

这里说关键字 new是一个 语法糖 是因为,它其实完全就是一些列数据变换过程的简写,仅此而已。你完全可以 自己手动实现一个 new 函数,这在本章之后的内容中会介绍。实事上只要你愿意在其很多面向过程的语言里面也可以来模拟,比如 C语言,这就是至今有大佬认JavaScript 能算面向对象的语言的原因,当然更存在强面向对象语言中一切皆可对象的说法,因为它有很多东西压根就不是对象,如一些类型的字面量。

1.4.1 从ES6 class 构造调用说起

ES6class语法糖中,构造函数名用constructor表示,比如:

classPerson {
constructor(name, birthday) {
this.name=name;
this.birthday=birthday;
  }
}

虽然本质不同,但用法和 Java 等语言中的类比较类似,如果你要创建 Person 类的实例,应该这样子:

letjack=newPerson("jcLee95", "07-30");
console.log(jack);

Out[]:

Person { name: 'jcLee95', birthday: '07-30' }

1.4.2 在 ES6规范 出现之前的 构造函数

更长的一段里 JavaScript 中是没有 class 语法糖 的,只能通过原始的 new函数(构造函数)的方式来进行调用。例如:

// 相当于 class 语法中的 constructor 函数。functionPerson(name, birthday) {
this.name=name;
this.birthday=birthday;
}

这与基于 class 的写法创建了一个一样的对象,因此对他们进行相同的调用将产生一样的效果:

letjack=newPerson("jcLee95", "07-30");
console.log(jack);

Out[]:

Person { name: 'jcLee95', birthday: '07-30' }

我们的思路始终是很清晰的那就是 从模拟 class 来,到 class 去,这就是我们先对 ES6 中 class 写法进行介绍的原因。我们对构造函数进行大写的原因在于,这是借用了 Javaclass 的习惯:构造方法 与 类 同名,比如:

// java 中的构造方法publicclassPerson{
Stringname;
Stringbirthday;
// 构造方法与类同名publicPerson(Stringname, Stringbirthday){
this.name=name;
this.birthday=birthday;
    }
}

但事实上,在 JavaScript 中,你可以使用 任意合法的标识符 作为构造函数名,虽然我们不推荐这样做。

2. 原型 与 原型链 的概念

2.1 原型

2.1.1 原型的概念

原型(prototype) 是 JavaScript 对象 上的 一个 特殊 属性,用于 共享数据,它被称作 原型对象原型 来源于 JavaScript 函数 ,我们每创建一个函数都有一个原型(prototype)属性 。因此,不论你使用将要把某个任意地 JavaScript 函数用作构造函数,比如以下地写法总是可以的:

functionPerson() {}
Person.prototype.name="jcLee95";
Person.prototype.birthday="07-30";
console.log(Person.prototype);

Out[]:

{ name: 'jcLee95', birthday: '07-30' }

2.1.2 关于 this 上下文

我们通过 函数名.prototype 的方式,可以对一个函数 原型对象 中的属性进行修改,但是 只有在使用 构造调用一个函数时,才能将函数体内部的 this动态地绑定到新创建的一个对象的上下文中。

这里的区别在于:

(1)函数在没有发生构造调用的情况下

  • 不会新创建一个对象 {} 作为被被构造出来的对象;
  • this 在全局执行环境中(在任何函数体外部)都指向 全局对象,如在浏览器中, window 对象同时也是全局对象;严格模式下将有所不同,函数内部this会若无赋值,将一直保持为undefined
    例如以下代码:
functionPerson() {
console.log("this 1 =",this)
}
Person();
console.log("this 2 = ",this);

在 nodeJS中结果为:

6666666666666.png

在浏览器中:

6666666666666.png

(2)函数在发生构造调用的情况下

这任然是JavaScript对面向对象语言的模拟的具体实现之一。一旦函数发生了构造调用,this就应该像 Java 等语言的某个中的this一样,将指向本类JavaScript 中,构造调过程中被创建的一个新对象就是模拟 Java 中被声明的一个类所代表的对象,这就不难理解:JavaScript 构造调用中,新对象将作为构造函数中的this的上下文,例如:

// 构造调用中的 this ,指向构造函数数所构造的类实例,它是一个在构造调用过程中创建的新的对象functionPerson(name, birthday) {
this.name=name;
this.birthday=birthday;
console.log(this);
}
newPerson("jcLee95", "07-30");

也可以使用 class 语法糖:

// 使用 ES6 class 语法糖 的等效方式classPerson {
constructor(name, birthday) {
this.name=name;
this.birthday=birthday;
console.log(this);
  }
}
newPerson("jcLee95", "07-30");

输出的结果都为:

Out[]:

Person { name: 'jcLee95', birthday: '07-30' }

可以看到, this已经不再是普通调用下的那个this

2.1.3 区分 prototype[[Prototype]]__proto__

前面我们已经说过,prototype是任意函数的一个属性,不一定是构造函数上的。

[[Prototype]] 可以认为是一种连接方式,用于链接实例构造函数原型构造调用 过程中创建了一个新的对象,即实例对象。而[[Prototype]] 就是在这个新对象创建后,该对象上用于 指向发生构造调用的函数(构造函数)的原型(即构造函数的prototype 属性)的指针,称作 原型指针

需要指出的是,一些资料不区分地将 prototype[[Prototype]] 都称作原型,这不太准确,也容易导致初学者误解概念。

[[Prototype]]来源于 ECMA-262规范中的定义,但实际在很多浏览器的实现中,为每个对象都提供了一个 __proto__ 的指针,它就相当于 [[Prototype]],如最主流的chrome、Firefox、Safari浏览器。

2.1.4 使用函数的方式实现 new(即所谓手写 new)

篇初已经指出构造调用中的new 不过是一个语法糖,用于模拟面向对象的类。我们介绍到这里已经陆续讲清楚了 JavaScript 中构造调用的各个环节的原理。现在只需要归纳 new 关键字在实际作用在一个函数时发送构造调用的具体步骤:

  • (1)创建一个空的简单JavaScript对象(即{});
  • (2)为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象
  • (3)将(1)中新创建的对象作为this的上下文 ;
  • (4)如果该函数没有返回对象,则返回this

为了加深读者对于这几个过程的理解,我们可以通过函数的形式自己实现一个与关键字 new 有着相同功能的函数:

// `myNew(cstrct, ...params)` be equal to `new cstrct(...params)`functionmyNew(cstrct, ...params){
// 1. create a new JavaScript object objconstobj= {};
// 2. Add the attribute `__proto__` to the newly created object (obj),// And link this property to the prototype object of the constructor.;obj.__proto__=cstrct.prototype; 
// 3. Take the new object (obj) as the context of this;// 3.1 Temporarily hang the constructor on obj.__proto__obj.__proto__._func=cstrct;
// 3.2 Execute this constructor to get "this"let_=obj._func(...params);
// 3.3 Delete the temporarily mounted attribute `_func` (otherwise this attribute will be added to the instance)deleteobj.__proto__._func// 4. If the function does not return an object, that is, null or undefined, it returns this; otherwise, it returns the object.return_instanceofObject?_ : obj;
}

你可以编写一个函数,使用自己编写的 new 函数调用试试:

// A function used as a constructorfunctionPerson(name, birthday) {
this.name=name;
this.birthday=birthday;
console.log(this);
}
// 构造调用,与使用 new 返回的效果完全一样myNew(Person, "jcLee95", "07-30")

Out[]:

Person { name: 'jcLee95', birthday: '07-30' }

2.2 原型链

至此我们已经了解过什么是 原型。那么试想一下,如果我们让一个对象的原型对象(prototype)恰好为另一个类型的实例会怎么样呢?

JavaScript 中⼏乎所有对象都可以访问 其构造器上的原型对象,而 原型对象(prototype)⼜可以访问 它⾃身的构造器上的原型对象(prototype)。以此类推,就像通过原型在原本独立的对象之间手拉着手不再独立,形成了一个 链式结构,即 原型链

JavaScript 中,原型链 是其实现 继承 的关键技术支撑,关于继承的相关内容,我们将在下一章进行介绍。

3. JavaScript 继承 的内部机制

继承 是面向对象编程的基本特点(抽象、封装、继承、多态)。上面讲了这么多,其实主要还是关于对象的创建于原型的。要称得上 面向对象编程,仅有这些是不够的,必须还要有 继承。早期的 JavaScript 的确从传统面向对象语言中借鉴很多,就比如继承。只是由于 JavaScript 自身本没有类的概念,只能通过已有的东西来 模拟继承。

3.1 从 Java 的继承说起

以Java为例,继承的本质作用是 允许创建分层次的类。面向对象编程中的继承表示的是生活中不同事物的所属关系,例如从生物学的意义上看人类是动物的一个子类别,那么可以以动物类所谓人类的父类。

// java 中的继承// 父类:动物类publicclassAnimal { 
privateStringname;
// 动物类(作为父类)的构造函数publicAnimal(StringmyName) { 
name=myName; 
    } 
publicvoideat(){ 
System.out.println(name+"在干饭"); 
    }
publicvoidsleep(){
System.out.println(name+"正在睡觉");
    }
}
// 子类:人类publicclassHumanextendsAnimal {
privateStringname;
// 人类(作为子类)的构造函数publicHuman(name){
// 调用父类的构造函数super(name);
    }
}

Java 语言中的 super 和 super()

Java语言中:

  • super 关键字可以来实现对父类成员的访问,用来引用当前对象的父类。
  • super 是一个函数,调用父类中构造器。
  • 子类是继承父类的构造方法的,它只是调用(隐式或显式)。
  • 如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
  • 如果父类构造器没有参数,则在 子类的构造方法 中不需要使用 super 关键字调用 父类构造方法,系统会自动调用父类的无参构造方法

在本例中, 人类(Human) 继承于 动物类(Animal)

3.2 ES6 class 语法中的继承

我们之所以要先讲 ES6 中基于 class 语法的继承,是因为该语法糖的用法已经更为接近 JavaScript 创造初要模拟继承的初忠。使用 ES6 的 class 改写上面的 Java 代码为 JavaScript 代码,你会发现形式上看起来是很接近的:

// JavaScript 中的继承(使用ES6 class语法)// 父类:动物类classAnimal { 
// 动物类(作为父类)的构造函数Animal(name) { 
this.name=name; 
  }
eat(){ 
console.log(this.name+"在干饭"); 
  }
sleep(){
console.log(this.name+"正在睡觉");
    }
  }
// 子类:人类classHumanextendsAnimal {
// 人类(作为子类)的构造函数Human(Stringname){
// 调用父类的构造函数super(name);
  }
}

JavaScript(ES6) 中的 super 和 super()

在 ES6 语法规范中:

  • super 关键字可以来实现对父类成员的访问,用来引用当前对象的父类。
  • super 是一个函数,调用父类中构造器。如果父类构造器中含有参数,应该按照父类构造器的参数规则传入 super()函数中。
  • 只能在派生类的构造函数中使用 super(),如果尝试在非 extends 关键字声明的类或函数中使用,则会导致程序抛出错误。
  • 在构造函数中访问 this 之前一定要先调用 super(),它 负责初始化 this,因此如果你在调用 super()前访问 this则会导致程序序抛出错误。
    例如将上面的程序中子了 Human 的构造函数改为:
classHumanextendsAnimal {
// 显示声明了子类(派生类)的构造函数,但没有调用 super()constructor(name){
  }
}
  • 这将导致在你运行程序时引发一个 ReferenceError
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  • 意思是在访问 this从派生构造函数 返回之前,必须调用派生类中的 super() 构造函数。6666666666666.png
  • 如果你不想在一个派生类的构造函数中调用 super(),则唯一的方法是让类的构造函数返回一个对象。
  • 如果你不显式声明派生类的构造函数,派生类将 “继承” 其父类的构造函数作为自己的构造函数。(实际上不是真实意义上的继承,而是在构造时在其原型链上调用了父类的构造函数)因此如果子类不需要对父类的构造函数进行装饰时,你完全可以选择不写子类的构造函数,在使用时只要按照父类的构造格式进行构造调用即可。

虽然我们这里又要提醒,JavaScript 继承背后的机理与 Java 不同。但是可以看到,ES6 中的 class 继承 语法上还是挺像的。

3.3 ES6 之前 JvaScript 中的继承

3.3.1 JavaScript 继承的特点 与 原型链

继承 是面向对象编程中最最重要的特征之一,被认为是 面向对象的基石。 ES6 标准后新增的伪类(class语法糖)已经和一般的面向对象语言写起来非常相似了。 但是毕竟 JavaScript 从一开始就是在模拟面向对象编程语言的行为特点,因此在其历史上也出现过很多种继承的实现方案。在 JavaScript 中提供了函数原型,这样我们可以使用原型链来完成实现继承,但这种继承和其它的语言的继承有内在的不同。

Java 为例,在Java的继承的过程中 子类 能够且仅能够从 父类 中获得 成员变量方法(不包括构造方法)、内部类(包括接口枚举

例如:

// 动物类publicclassAnimal { 
privateStringname;
publicAnimal(StringmyName) { 
name=myName; 
    } 
publicvoideat(){ 
System.out.println(name+" is eating!");  
    }
publicvoidsleep(){
System.out.println(name+"is sleeping!"); 
    }
}
// 人类 继承于 动物类,可以获得 Animal 类的成员变量、方法、内部类publicclassHumanextendsAnimal {;
publicHuman(Stringname){
super(name);
    }
}
publicclassRun{
publicstaticvoidmain(String[] args) {
Humana=newHuman("Human");
// 调用人类从动物类继承过来的 eat 方法a.eat();
    }
}

编译,运行:

6666666666666.png


6666666666666.png

可以看到,虽然Human类没有定义eat方法,但是从父类Animal获得了该方法。

Java 的继承中,实际上子类中所获得的方法、成员等,都是父类中相应成员的复制。在子类中重写相应的方法能够覆盖父类中的方法。

那么 JavaScript 呢?还记得这段代码吗:

classAnimal{
constructor(name){
this.name=name;
    }
eat(){
console.log(this.name+" is eating!")
    }
sleep(){
console.log(this.name+" is sleeping!")
    }
}
classHumanextendsAnimal{
constructor(name,nationality){
super(name);
this.nationality=nationality;
    }
think(){
console.log(this.name+" is thinking!")
    }
}
letperson=newHuman("jcLee95", "China");
console.log(person);
person.eat();
person.sleep();
person.think();

看起来简直不能和Java说很像,但是在继承的原理上看:

  • 子类实例要访问父类的方法,实际上是通过子类实例对象 的 原型指针 __proto__指向子类构造函数的原型对象(prototype属性),这个原型对象中,也包含一个原型指针,指向了父类构造函数的原型。
  • 事实上,在 JavaScript 中当代码读取某个对象的某个属性中,有一套这样的查询过程,是该语言比较有特点的地方:
  • 从对象实例本身的属性开始搜索:
  • 如果在实例中找到了具有给定名字的属性,则返回该属性的值;
  • 如果在实例中找不到:则继续收缩 原型指针(__proto__)所指向的原型对象(prototype),在该对象中继续查找具有给定名字的属性,如果找到,则返回该属性的值。

3.3.2 刨析原型链继承的过程:手写其详细步骤

3.3.2.1 父类的函数表示

(1)父类的构造函数
functionAnimal(name){
this.name=name;
}
(2)父类的成员
letAnimalPrototypes= {
eat:function() {
console.log(this.name+" is eating!")
  },
sleep:function() {
console.log(this.name+" is sleeping!")
  }
}
for(letiinAnimalPrototypes) {
Animal.prototype[i] =AnimalPrototypes[i];
}

说明

从基本功能上看,这种直接给 prototype赋值的方法也可以实现,而且看起来更简单:

Animal.prototype= {
eat:function() {
console.log(this.name+" is eating!")
  },
sleep:function() {
console.log(this.name+" is sleeping!")
  }
}

但是这会覆盖Animal.prototype原先包含的信息,使得使用Animal作为构造函数创建实例时,看起来像普通对象:

6666666666666.png

可以看到这个例子构建的实例,在 NodeJS 中打印成了,{ name: 'animal_instance' },不能很好地表示该对象是通过 new 构建的 Animal 类的实例,这有时候在编程中不方便我们调试。

而使用Animal.prototype[i]这种添加对象中的键值对的方式:

6666666666666.png

可以看到,打印的实例对象结果为:

Animal { name: 'animal_instance' }

很直观地显示出了这个对象是 Animal 构造函数经过构造调用而创建的 Animal 类的实例。

3.3.2.2 子类的函数表示

(1)子类的构造函数

子类的构造函数中是需要调用父类的构造函数的,使用 ES6的 class 语法时我们是这样写的:

constructor(name,nationality){
super(name);
this.nationality=nationality;
}

可见分为两个部分,第一个部分是父类的构造函数,它需要传入父类构造函数需要使用的参数,这个例子中是name,一般来说父类构造时也需要绑定一些参数,两个构造函数中的 this 应该指向同一上下文,因此在子类中调用父类的构造函数一定有动态更改父类构造函数的 this 到之类构造函数当中,这个国产被隐含在 super(...)之中了。

另外一个部分是子类特有的构造过程,相当于在构造方面子类对父类的扩展。比如这个例子中人类相对于动物类会更关注国籍,因此在完成动物类都有构造后,还要完成人类特有的国籍属性绑定。

本例中,子类(人类)的构造函数可以这样写:

functionHuman(super_instance, name, nationality){
super_instance._superConstructor.call(this,name);
deletesuper_instance.__proto__._superConstructor; 
this.nationality=nationality;
}
(2)子类的成员

这里我们仅仅是先声明好子类的成员到一个对象,需要在后续的继承函数中将这个对象中的属性逐个添加到子类实例。

letHuman_PropMembers= {
think : function(){
console.log(this.name+" is thinking!")
  }
}

3.3.2.3 原型继承函数的实现

在这个函数中我们实现代码会更通用和原始一些,不会直接去使用 new 来创建对象的实例,因为直接使用 new 不仅不好显示继承的完整步骤,也不好处理子类父类都含有需要绑定的参数且参数不同的情况。

functionextendsNew(superConstructor, subConstructor, superParams, subPatams, subPropMembers) {
// Used as the super class instance object to be constructedconstsuper_instance= {};
super_instance.__proto__=superConstructor.prototype;
// Temporarily mount the parent class constructorsuper_instance.__proto__._superConstructor=superConstructor;
// The prototype of the subclass constructor is the parent class instance, that is, let the subclass inherit the parent class. // At this time, this instance of the parent class has not been constructed.// That is, the parent class constructor is called, and the parent class constructor will be called in the subclass constructor.for(letiinsuper_instance){
subConstructor.prototype[i] =super_instance[i];
  }
// An object used as an instance of the subclass to be constructed. constsub_instance= {};
sub_instance.__proto__=subConstructor.prototype;
// Add subclass members to the prototype of constructor.for (letiinsubPropMembers) {
subConstructor.prototype[i] =subPropMembers[i];
  }
// Constructor temporarily hung on subclasses on subclassessub_instance.__proto__._subConstructor=subConstructor; 
// Execute subclass constructor to complete other things of subclass instance construction.// At this time, it should be called in the constructor of the subclass, and the constructor of the parent class, namely `super(...params)`, must be called first.// It is temporarily hung on the instance `super_instance` of the parent class, and should be deleted when it is used up.let_=sub_instance._subConstructor(super_instance, ...superParams, ...subPatams); 
// After the subclass instance is constructed, the constructor is no longer needed on the instance, // so we need to delete the constructor on the subclass instance.deletesub_instance.__proto__._subConstructor; 
return_instanceofObject?_ : sub_instance;
}

3.3.2.4 完整代码与测试

// by jcLee95 // https://blog.csdn.net/qq_28550263/article/details/126373011// ----------------------------- Define Animal class ----------------------------- // class Animal's constructor// Which will be used as superclass's constructorfunctionAnimal(name){
this.name=name;
}
for(letiinAnimalPrototypes) {
Animal.prototype[i] =AnimalPrototypes[i];
}
// 不建议直接使用 Animal.prototype = {...} 的方式,因为这样会覆盖掉隐藏在 父类构造器上的 对象名letAnimalPrototypes= {
eat:function() {
console.log(this.name+" is eating!")
  },
sleep:function() {
console.log(this.name+" is sleeping!")
  }
}
for(letiinAnimalPrototypes) {
Animal.prototype[i] =AnimalPrototypes[i];
}
// ----------------------------- Define Human class ----------------------------- // class Human's constructor// Which will be used as subclass's constructorfunctionHuman(super_instance, name, nationality){
// Execute other operations required for parent class construction first: super(...params);// 【强调】:这里必须动态更改父类构造函数的 this 上下文与子类的 this 上下文一致//          否则,在父类构造器中绑定到 this 的数据不会指向子类实例!// 你可以直接调用 JavaScript 函数的 .call 方法,该方法的第一个参数就是要动态改变的 this 上下文super_instance._superConstructor.call(this,name);
deletesuper_instance.__proto__._superConstructor; // Delete temporarily mounted superclass constructor.// Execute other operations of subclass construction.this.nationality=nationality;
}
letHuman_PropMembers= {
think : function(){
console.log(this.name+" is thinking!")
  }
}
// ----------------------------- Make Human extends Animal ----------------------------- // Prototype chain inheritance principle (完全手写继承原理)functionextendsNew(superConstructor, subConstructor, superParams, subPatams, subPropMembers) {
// 用作待构造的父类实例对象constsuper_instance= {};
// 父类实例的原型指针指向父类构造函数的原型super_instance.__proto__=superConstructor.prototype;
// 临时挂载父类构造函数super_instance.__proto__._superConstructor=superConstructor;
// 子类构造器原型 为 父类实例,也就是让子类继承父类,这时候父类的该实例也还没有完成构造,即调用父类构造函数,而父类构造函数将再子类构造函数中调用// 不建议使用 subConstructor.prototype = super_instance;,因为这会覆盖掉隐藏在 subConstructor 对象名for(letiinsuper_instance){
subConstructor.prototype[i] =super_instance[i];
  }
// 用作待构造的子类实例的对象 constsub_instance= {};
// 子类实例原型指针,指向子类构造器原型sub_instance.__proto__=subConstructor.prototype;
// 添加子类成员到构造函数的原型for (letiinsubPropMembers) {
subConstructor.prototype[i] =subPropMembers[i];
  }
// 子类实例上临时挂在子类的构造函数sub_instance.__proto__._subConstructor=subConstructor; 
// 执行子类构造函数,完成子类实例构造的其它事情。这个时候,在子类的构造函数中应该调用,且必须先调用父类的构造函数,即 super(),它被临时挂在在父类的实例 super_instance 上,用完应该删除let_=sub_instance._subConstructor(super_instance, ...superParams, ...subPatams); 
// 构造子类实例后,实例上不再需要构造函数,因此删除子类实例上的构造函数deletesub_instance.__proto__._subConstructor; 
return_instanceofObject?_ : sub_instance;
}
letperson=extendsNew(Animal, Human, ["jcLee95"], ["China"], Human_PropMembers);
console.log(person);
person.eat();
person.sleep();
person.think();

Out[]:

Human { name: 'jcLee95', nationality: 'China' }
jcLee95 is eating!
jcLee95 is sleeping!
jcLee95 is thinking!


6666666666666.png

目录
相关文章
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
31 0
|
1月前
|
设计模式 JavaScript 前端开发
深入理解 JavaScript 中的绑定机制(下)
深入理解 JavaScript 中的绑定机制(下)
|
1月前
|
JavaScript 前端开发
深入理解 JavaScript 中的绑定机制(上)
深入理解 JavaScript 中的绑定机制(上)
|
3月前
|
JavaScript 前端开发 数据库连接
js的异常程序处理机制
js的异常程序处理机制
18 0
|
1月前
|
开发框架 JavaScript 前端开发
描述JavaScript事件循环机制,并举例说明在游戏循环更新中的应用。
JavaScript的事件循环机制是单线程处理异步操作的关键,由调用栈、事件队列和Web APIs构成。调用栈执行函数,遇到异步操作时交给Web APIs,完成后回调函数进入事件队列。当调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中常用框架提供更高级别的抽象。
14 1
|
1月前
|
JavaScript 前端开发 算法
深入探讨前端框架Vue.js中的虚拟DOM机制
本文将深入探讨前端框架Vue.js中的虚拟DOM机制,分析其原理、优势以及在实际开发中的应用场景,帮助读者更好地理解Vue.js框架的核心特性。
|
1月前
|
消息中间件 前端开发 JavaScript
深入理解JavaScript中的事件循环机制
JavaScript作为一种前端开发必备的编程语言,在处理异步操作时常常涉及到事件循环机制。本文将深入探讨JavaScript中事件循环的工作原理,帮助读者更好地理解和运用这一关键概念。
|
1月前
|
自然语言处理 JavaScript 前端开发
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)
|
1月前
|
JavaScript 前端开发
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(上)
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(上)
|
2月前
|
消息中间件 存储 前端开发
理解JavaScript事件循环机制
理解JavaScript事件循环机制
18 0