찾다
웹 프론트엔드JS 튜토리얼Javascript 기술 Stack_기본 지식의 4가지 종속성 주입 요약

객체 지향 프로그래밍에서 제어 반전(IoC)을 달성하기 위한 가장 일반적인 기술적 수단 중 하나로 DI(종속성 주입)는 OOP 프로그래밍에서 오랫동안 인기를 끌었습니다. 예를 들어 J2EE에는 유명한 리더인 Spring이 있습니다. 당연히 Javascript 커뮤니티에서도 활발히 시도되고 있는 AngularJS는 대부분 DI를 기반으로 구현됩니다. 불행히도, 리플렉션 메커니즘이 부족하고 Annotation 구문을 지원하지 않는 동적 언어인 Javascript는 오랫동안 자체 Spring 프레임워크를 갖지 못했습니다. 물론 ECMAScript 초안이 빠른 반복 기간에 접어들면서 Javascript 커뮤니티의 다양한 방언과 프레임워크가 등장하고 상승세를 보이고 있습니다. 뛰어난 JavascriptDI 프레임워크가 등장하는 것은 시간 문제일 뿐이라는 것을 예견할 수 있습니다.

이 기사에서는 Javascript의 일반적인 종속성 주입 방법을 요약하고 inversify.js를 예로 들어 Javascript의 DI 프레임워크에 대한 방언 커뮤니티의 시도와 예비 결과를 소개합니다. 기사는 네 개의 섹션으로 구성되어 있습니다.

1. 인젝터, 캐시, 함수 매개변수 이름을 기반으로 의존성 주입
2. AngularJS의 이중 인젝터를 기반으로 한 의존성 주입
3. TypeScript의 데코레이터 및 리플렉션을 기반으로 한 종속성 주입
4. inversify.js - Javascript 기술 스택의 IoC 컨테이너

1. 인젝터, 캐시, 함수 매개변수 이름을 기반으로 의존성 주입

Javascript는 기본적으로 리플렉션(Reflection) 구문을 지원하지 않지만 Function.prototype의 toString 메서드는 런타임 시 함수의 내부 구조를 감시할 수 있는 또 다른 방법을 제공합니다. 문자열 형식 function 키워드를 포함하여 전체 함수 정의를 반환합니다. 이 완전한 함수 정의부터 시작하여 정규식을 사용하여 함수에 필요한 매개변수를 추출함으로써 함수의 실행 종속성을 어느 정도 알 수 있습니다.
예를 들어, Student 클래스에 있는 write 메소드의 함수 시그니처인 write(notebook, pencil)는 해당 실행이 Notebook 및 pencil 객체에 따라 달라짐을 보여줍니다. 따라서 먼저 노트북과 연필 개체를 캐시에 저장한 다음 인젝터를 통해 필요한 종속성과 함께 쓰기 메서드를 제공할 수 있습니다.

var cache = {};
// 通过解析Function.prototype.toString()取得参数名
function getParamNames(func) {
  // 正则表达式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
  var paramNames = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1];
  paramNames = paramNames.replace(/ /g, '');
  paramNames = paramNames.split(',');
  return paramNames;
}
var injector = {
  // 将func作用域中的this关键字绑定到bind对象上,bind对象可以为空
  resolve: function (func, bind) {
    // 取得参数名
    var paramNames = getParamNames(func);
    var params = [];
    for (var i = 0; i < paramNames.length; i++) {
      // 通过参数名在cache中取出相应的依赖
      params.push(cache[paramNames[i]]);
    }
    // 注入依赖并执行函数
    func.apply(bind, params);
  }
};
 
function Notebook() {}
Notebook.prototype.printName = function () {
  console.log('this is a notebook');
};
 
function Pencil() {}
Pencil.prototype.printName = function () {
  console.log('this is a pencil');
};
 
function Student() {}
Student.prototype.write = function (notebook, pencil) {
  if (!notebook || !pencil) {
    throw new Error('Dependencies not provided!');
  }
  console.log('writing...');
};
// 提供notebook依赖
cache['notebook'] = new Notebook();
// 提供pencil依赖
cache['pencil'] = new Pencil();
var student = new Student();
injector.resolve(student.write, student); // writing...

때때로 좋은 캡슐화를 보장하기 위해 캐시 개체를 외부 범위에 노출할 필요가 없는 경우가 종종 있는데, 이는 클로저 변수나 개인 속성의 형태로 존재합니다.

function Injector() {
  this._cache = {};
}
 
Injector.prototype.put = function (name, obj) {
  this._cache[name] = obj;
};
 
Injector.prototype.getParamNames = function (func) {
  // 正则表达式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
  var paramNames = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1];
  paramNames = paramNames.replace(/ /g, '');
  paramNames = paramNames.split(',');
  return paramNames;
};
 
Injector.prototype.resolve = function (func, bind) {
  var self = this;
  var paramNames = self.getParamNames(func);
  var params = paramNames.map(function (name) {
    return self._cache[name];
  });
  func.apply(bind, params);
};
 
var injector = new Injector();
 
var student = new Student();
injector.put('notebook', new Notebook());
injector.put('pencil', new Pencil())
injector.resolve(student.write, student); // writing...

예를 들어, 이제 Student 클래스에서 draw(notebook, pencil, eraser)라는 또 다른 메소드 함수를 실행하려고 합니다. 인젝터의 캐시에 이미 노트북과 연필 객체가 있으므로 캐시에 추가 지우개만 저장하면 됩니다.

function Eraser() {}
Eraser.prototype.printName = function () {
  console.log('this is an eraser');
};
 
// 为Student增加draw方法
Student.prototype.draw = function (notebook, pencil, eraser) {
  if (!notebook || !pencil || !eraser) {
    throw new Error('Dependencies not provided!');
  }
  console.log('drawing...');
};
 
injector.put('eraser', new Eraser());
injector.resolve(student.draw, student);

종속성 주입을 통해 함수 실행과 함수가 의존하는 개체의 생성 논리가 분리됩니다.
물론, grunt/gulp/fis와 같은 프런트엔드 엔지니어링 도구의 인기로 인해 점점 더 많은 프로젝트가 온라인에 진출하기 전에 코드 난독화(uglify)를 거쳤습니다. 따라서 매개변수 이름을 통해 종속성을 판단하는 것이 항상 신뢰할 수 있는 것은 아닙니다. 함수에 추가 속성을 추가하여 명시적으로 명시할 수도 있습니다.

Student.prototype.write.depends = ['notebook', 'pencil'];
Student.prototype.draw.depends = ['notebook', 'pencil', 'eraser'];
Injector.prototype.resolve = function (func, bind) {
  var self = this;
  // 首先检查func上是否有depends属性,如果没有,再用正则表达式解析
  func.depends = func.depends || self.getParamNames(func);
  var params = func.depends.map(function (name) {
    return self._cache[name];
  });
  func.apply(bind, params);
};
var student = new Student();
injector.resolve(student.write, student); // writing...
injector.resolve(student.draw, student); // draw...

二. AngularJS中基于双Injector的依赖注入

熟悉AngularJS的同学很快就能联想到,在injector注入之前,我们在定义module时还可以调用config方法来配置随后会被注入的对象。典型的例子就是在使用路由时对$routeProvider的配置。也就是说,不同于上一小节中直接将现成对象(比如new Notebook())存入cache的做法,AngularJS中的依赖注入应该还有一个”实例化”或者”调用工厂方法”的过程。
这就是providerInjector、instanceInjector以及他们各自所拥有的providerCache和instanceCache的由来。
在AngularJS中,我们能够通过依赖注入获取到的injector通常是instanceInjector,而providerInjector则是以闭包中变量的形式存在的。每当我们需要AngularJS提供依赖注入服务时,比如想要获取notebook,instanceInjector会首先查询instanceCache上是存在notebook属性,如果存在,则直接注入;如果不存在,则将这个任务转交给providerInjector;providerInjector会将”Provider”字符串拼接到”notebook”字符串的后面,组成一个新的键名”notebookProvider”,再到providerCache中查询是否有notebookProvider这个属性,如有没有,则抛出异常Unknown Provider异常:

如果有,则将这个provider返回给instanceInjector;instanceInjector拿到notebookProvider后,会调用notebookProvider上的工厂方法$get,获取返回值notebook对象,将该对象放到instanceCache中以备将来使用,同时也注入到一开始声明这个依赖的函数中。

需要注意的是,AngularJS中的依赖注入方式也是有缺陷的:利用一个instanceInjector单例服务全局的副作用就是无法单独跟踪和控制某一条依赖链条,即使在没有交叉依赖的情况下,不同module中的同名provider也会产生覆盖,这里就不详细展开了。

另外,对于习惯于Java和C#等语言中高级IoC容器的同学来说,看到这里可能觉得有些别扭,毕竟在OOP中,我们通常不会将依赖以参数的形式传递给方法,而是作为属性通过constructor或者setters传递给实例,以实现封装。的确如此,一、二节中的依赖注入方式没有体现出足够的面向对象特性,毕竟这种方式在Javascript已经存在多年了,甚至都不需要ES5的语法支持。希望了解Javascript社区中最近一两年关于依赖注入的研究和成果的同学,可以继续往下阅读。

三. TypeScript中基于装饰器和反射的依赖注入

博主本身对于Javascript的各种方言的学习并不是特别热情,尤其是现在EMCAScript提案、草案更新很快,很多时候借助于polyfill和babel的各种preset就能满足需求了。但是TypeScript是一个例外(当然现在Decorator也已经是提案了,虽然阶段还比较早,但是确实已经有polyfill可以使用)。上文提到,Javascript社区中迟迟没有出现一款优秀的IoC容器和自身的语言特性有关,那就依赖注入这个话题而言,TypeScript给我们带来了什么不同呢?至少有下面这几点:
* TypeScript增加了编译时类型检查,使Javascript具备了一定的静态语言特性
* TypeScript支持装饰器(Decorator)语法,和传统的注解(Annotation)颇为相似
* TypeScript支持元信息(Metadata)反射,不再需要调用Function.prototype.toString方法
下面我们就尝试利用TypeScript带来的新语法来规范和简化依赖注入。这次我们不再向函数或方法中注入依赖了,而是向类的构造函数中注入。
TypeScript支持对类、方法、属性和函数参数进行装饰,这里需要用到的是对类的装饰。继续上面小节中用到的例子,利用TypeScript对代码进行一些重构:

class Pencil {
  public printName() {
    console.log('this is a pencil');
  }
}
 
class Eraser {
  public printName() {
    console.log('this is an eraser');
  }
}
 
class Notebook {
  public printName() {
    console.log('this is a notebook');
  }
}
 
class Student {
  pencil: Pencil;
  eraser: Eraser;
  notebook: Notebook;
  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  public write() {
    if (!this.notebook || !this.pencil) {
      throw new Error('Dependencies not provided!');
    }
    console.log('writing...');
  }
  public draw() {
    if (!this.notebook || !this.pencil || !this.eraser) {
      throw new Error('Dependencies not provided!');
    }
    console.log('drawing...');
  }
}

下面是injector和装饰器Inject的实现。injector的resolve方法在接收到传入的构造函数时,会通过name属性取出该构造函数的名字,比如class Student,它的name属性就是字符串”Student”。再将Student作为key,到dependenciesMap中去取出Student的依赖,至于dependenciesMap中是何时存入的依赖关系,这是装饰器Inject的逻辑,后面会谈到。Student的依赖取出后,由于这些依赖已经是构造函数的引用而非简单的字符串了(比如Notebook、Pencil的构造函数),因此直接使用new语句即可获取这些对象。获取到Student类所依赖的对象之后,如何把这些依赖作为构造函数的参数传入到Student中呢?最简单的莫过于ES6的spread操作符。在不能使用ES6的环境下,我们也可以通过伪造一个构造函数来完成上述逻辑。注意为了使instanceof操作符不失效,这个伪造的构造函数的prototype属性应该指向原构造函数的prototype属性。

var dependenciesMap = {};
var injector = {
  resolve: function (constructor) {
    var dependencies = dependenciesMap[constructor.name];
    dependencies = dependencies.map(function (dependency) {
      return new dependency();
    });
    // 如果可以使用ES6的语法,下面的代码可以合并为一行:
    // return new constructor(...dependencies);
    var mockConstructor: any = function () {
      constructor.apply(this, dependencies);
    };
    mockConstructor.prototype = constructor.prototype;
    return new mockConstructor();
  }
};
function Inject(...dependencies) {
  return function (constructor) {
    dependenciesMap[constructor.name] = dependencies;
    return constructor;
  };
}

injector和装饰器Inject的逻辑完成后,就可以用来装饰class Student并享受依赖注入带来的乐趣了:

// 装饰器的使用非常简单,只需要在类定义的上方添加一行代码
// Inject是装饰器的名字,后面是function Inject的参数
@Inject(Notebook, Pencil, Eraser)
class Student {
  pencil: Pencil;
  eraser: Eraser;
  notebook: Notebook;
  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  public write() {
    if (!this.notebook || !this.pencil) {
      throw new Error('Dependencies not provided!');
    }
    console.log('writing...');
  }
  public draw() {
    if (!this.notebook || !this.pencil || !this.eraser) {
      throw new Error('Dependencies not provided!');
    }
    console.log('drawing...');
  }
}
var student = injector.resolve(Student);
console.log(student instanceof Student); // true
student.notebook.printName(); // this is a notebook
student.pencil.printName(); // this is a pencil
student.eraser.printName(); // this is an eraser
student.draw(); // drawing
student.write(); // writing

利用装饰器,我们还可以实现一种比较激进的依赖注入,下文称之为RadicalInject。RadicalInject对原代码的侵入性比较强,不一定适合具体的业务,这里也一并介绍一下。要理解RadicalInject,需要对TypeScript装饰器的原理和Array.prototype上的reduce方法理解比较到位。

function RadicalInject(...dependencies){
  var wrappedFunc:any = function (target: any) {
    dependencies = dependencies.map(function (dependency) {
      return new dependency();
    });
    // 使用mockConstructor的原因和上例相同
    function mockConstructor() {
      target.apply(this, dependencies);
    }
    mockConstructor.prototype = target.prototype;
 
    // 为什么需要使用reservedConstructor呢?因为使用RadicalInject对Student方法装饰之后,
    // Student指向的构造函数已经不是一开始我们声明的class Student了,而是这里的返回值,
    // 即reservedConstructor。Student的指向变了并不是一件不能接受的事,但是如果要
    // 保证student instanceof Student如我们所期望的那样工作,这里就应该将
    // reservedConstructor的prototype属性指向原Student的prototype
    function reservedConstructor() {
      return new mockConstructor();
    }
    reservedConstructor.prototype = target.prototype;
    return reservedConstructor;
  }
  return wrappedFunc;
}

使用RadicalInject,原构造函数实质上已经被一个新的函数代理了,使用上也更为简单,甚至都不需要再有injector的实现:

@RadicalInject(Notebook, Pencil, Eraser)
class Student {
  pencil: Pencil;
  eraser: Eraser;
  notebook: Notebook;
  public constructor() {}
  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  public write() {
    if (!this.notebook || !this.pencil) {
      throw new Error('Dependencies not provided!');
    }
    console.log('writing...');
  }
  public draw() {
    if (!this.notebook || !this.pencil || !this.eraser) {
      throw new Error('Dependencies not provided!');
    }
    console.log('drawing...');
  }
}
// 不再出现injector,直接调用构造函数
var student = new Student(); 
console.log(student instanceof Student); // true
student.notebook.printName(); // this is a notebook
student.pencil.printName(); // this is a pencil
student.eraser.printName(); // this is an eraser
student.draw(); // drawing
student.write(); // writing

由于class Student的constructor方法需要接收三个参数,直接无参调用new Student()会造成TypeScript编译器报错。当然这里只是分享一种思路,大家可以暂时忽略这个错误。有兴趣的同学也可以使用类似的思路尝试代理一个工厂方法,而非直接代理构造函数,以避免这类错误,这里不再展开。

AngularJS2团队为了获得更好的装饰器和反射语法的支持,一度准备另起炉灶,基于AtScript(AtScript中的”A”指的就是Annotation)来进行新框架的开发。但最终却选择拥抱TypeScript,于是便有了微软和谷歌的奇妙组合。

当然,需要说明的是,在缺少相关标准和浏览器厂商支持的情况下,TypeScript在运行时只是纯粹的Javascript,下节中出现的例子会印证这一点。

四. inversify.js——Javascript技术栈中的IoC容器

其实从Javascript出现各种支持高级语言特性的方言就可以预见到,IoC容器的出现只是早晚的事情。比如博主今天要介绍的基于TypeScript的inversify.js,就是其中的先行者之一。
inversity.js比上节中博主实现的例子还要进步很多,它最初设计的目的就是为了前端工程师同学们能在Javascript中写出符合SOLID原则的代码,立意可谓非常之高。表现在代码中,就是处处有接口,将”Depend upon Abstractions. Do not depend upon concretions.”(依赖于抽象,而非依赖于具体)表现地淋漓尽致。继续使用上面的例子,但是由于inversity.js是面向接口的,上面的代码需要进一步重构:

interface NotebookInterface {
  printName(): void;
}
interface PencilInterface {
  printName(): void;
}
interface EraserInterface {
  printName(): void;
}
interface StudentInterface {
  notebook: NotebookInterface;
  pencil: PencilInterface;
  eraser: EraserInterface;
  write(): void;
  draw(): void;
}
class Notebook implements NotebookInterface {
  public printName() {
    console.log('this is a notebook');
  }
}
class Pencil implements PencilInterface {
  public printName() {
    console.log('this is a pencil');
  }
}
class Eraser implements EraserInterface {
  public printName() {
    console.log('this is an eraser');
  }
}
 
class Student implements StudentInterface {
  notebook: NotebookInterface;
  pencil: PencilInterface;
  eraser: EraserInterface;
  constructor(notebook: NotebookInterface, pencil: PencilInterface, eraser: EraserInterface) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  write() {
    console.log('writing...');
  }
  draw() {
    console.log('drawing...');
  }
}

由于使用了inversity框架,这次我们就不用自己实现injector和Inject装饰器啦,只需要从inversify模块中引用相关对象:

import { Inject } from "inversify";
 
@Inject("NotebookInterface", "PencilInterface", "EraserInterface")
class Student implements StudentInterface {
  notebook: NotebookInterface;
  pencil: PencilInterface;
  eraser: EraserInterface;
  constructor(notebook: NotebookInterface, pencil: PencilInterface, eraser: EraserInterface) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  write() {
    console.log('writing...');
  }
  draw() {
    console.log('drawing...');
  }
}

这样就行了吗?还记得上节中提到TypeScript中各种概念只是语法糖吗?不同于上一节中直接将constructor引用传递给Inject的例子,由于inversify.js是面向接口的,而诸如NotebookInterface、PencilInterface之类的接口只是由TypeScript提供的语法糖,在运行时并不存在,因此我们在装饰器中声明依赖时只能使用字符串形式而非引用形式。不过不用担心,inversify.js为我们提供了bind机制,在接口的字符串形式和具体的构造函数之间搭建了桥梁:

import { TypeBinding, Kernel } from "inversify";
 
var kernel = new Kernel();
kernel.bind(new TypeBinding("NotebookInterface", Notebook));
kernel.bind(new TypeBinding("PencilInterface", Pencil));
kernel.bind(new TypeBinding("EraserInterface", Eraser));
kernel.bind(new TypeBinding("StudentInterface", Student));

注意这步需要从inversify模块中引入TypeBinding和Kernel,并且为了保证返回值类型以及整个编译时静态类型检查能够顺利通过,泛型语法也被使用了起来。
说到这里,要理解new TypeBinding(“NotebookInterface”, Notebook)也就很自然了:为依赖于”NotebookInterface”字符串的类提供Notebook类的实例,返回值向上溯型到NotebookInterface。
完成了这些步骤,使用起来也还算顺手:

var student: StudentInterface = kernel.resolve("StudentInterface");
console.log(student instanceof Student); // true
student.notebook.printName(); // this is a notebook
student.pencil.printName(); // this is a pencil
student.eraser.printName(); // this is an eraser
student.draw(); // drawing
student.write(); // writing

마지막으로 ECMAScript 관련 제안의 현황과 진행 상황에 대해 말씀드리겠습니다. Google의 AtScript 팀은 Annotation에 대한 제안을 한 적이 있었지만 AtScript는 사산되었으므로 제안은 자연스럽게 사라졌습니다. 현재 es7 표준이 될 것으로 더 유망한 것은 데코레이터에 대한 제안입니다: https://github.com/wycats/javascript- decorators. 관심 있는 학생들은 관련 github 페이지에서 후속 조치를 취할 수 있습니다. DI는 OOP 프로그래밍의 많은 모드와 기능 중 하나일 뿐이지만 OOP에서 Javascript가 취하는 어려운 경로를 반영할 수 있습니다. 하지만 전체적으로 보면 길은 순탄하고 미래는 밝다고 할 수 있습니다. 의존성 주입이라는 주제로 돌아가서, 한쪽에는 간절히 기다려온 Javascript 커뮤니티가 있고 다른 한쪽에는 뒤늦은 IoC 컨테이너가 있는데, 두 가지가 결국 어떤 종류의 화학 반응을 일으킬지 지켜보겠습니다.

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
Python vs. JavaScript : 개발자를위한 비교 분석Python vs. JavaScript : 개발자를위한 비교 분석May 09, 2025 am 12:22 AM

Python과 JavaScript의 주요 차이점은 유형 시스템 및 응용 프로그램 시나리오입니다. 1. Python은 과학 컴퓨팅 및 데이터 분석에 적합한 동적 유형을 사용합니다. 2. JavaScript는 약한 유형을 채택하며 프론트 엔드 및 풀 스택 개발에 널리 사용됩니다. 두 사람은 비동기 프로그래밍 및 성능 최적화에서 고유 한 장점을 가지고 있으며 선택할 때 프로젝트 요구 사항에 따라 결정해야합니다.

Python vs. JavaScript : 작업에 적합한 도구 선택Python vs. JavaScript : 작업에 적합한 도구 선택May 08, 2025 am 12:10 AM

Python 또는 JavaScript를 선택할지 여부는 프로젝트 유형에 따라 다릅니다. 1) 데이터 과학 및 자동화 작업을 위해 Python을 선택하십시오. 2) 프론트 엔드 및 풀 스택 개발을 위해 JavaScript를 선택하십시오. Python은 데이터 처리 및 자동화 분야에서 강력한 라이브러리에 선호되는 반면 JavaScript는 웹 상호 작용 및 전체 스택 개발의 장점에 없어서는 안될 필수입니다.

파이썬 및 자바 스크립트 : 각각의 강점을 이해합니다파이썬 및 자바 스크립트 : 각각의 강점을 이해합니다May 06, 2025 am 12:15 AM

파이썬과 자바 스크립트는 각각 고유 한 장점이 있으며 선택은 프로젝트 요구와 개인 선호도에 따라 다릅니다. 1. Python은 간결한 구문으로 데이터 과학 및 백엔드 개발에 적합하지만 실행 속도가 느립니다. 2. JavaScript는 프론트 엔드 개발의 모든 곳에 있으며 강력한 비동기 프로그래밍 기능을 가지고 있습니다. node.js는 풀 스택 개발에 적합하지만 구문은 복잡하고 오류가 발생할 수 있습니다.

JavaScript의 핵심 : C 또는 C에 구축 되었습니까?JavaScript의 핵심 : C 또는 C에 구축 되었습니까?May 05, 2025 am 12:07 AM

javaScriptisNotBuiltoncorc; it'SangretedLanguageThatrunsonOngineStenWrittenInc .1) javaScriptWasDesignEdasAlightweight, 해석 hanguageforwebbrowsers.2) Endinesevolvedfromsimpleplemporectreterstoccilpilers, 전기적으로 개선된다.

JavaScript 응용 프로그램 : 프론트 엔드에서 백엔드까지JavaScript 응용 프로그램 : 프론트 엔드에서 백엔드까지May 04, 2025 am 12:12 AM

JavaScript는 프론트 엔드 및 백엔드 개발에 사용할 수 있습니다. 프론트 엔드는 DOM 작업을 통해 사용자 경험을 향상시키고 백엔드는 Node.js를 통해 서버 작업을 처리합니다. 1. 프론트 엔드 예 : 웹 페이지 텍스트의 내용을 변경하십시오. 2. 백엔드 예제 : node.js 서버를 만듭니다.

Python vs. JavaScript : 어떤 언어를 배워야합니까?Python vs. JavaScript : 어떤 언어를 배워야합니까?May 03, 2025 am 12:10 AM

Python 또는 JavaScript는 경력 개발, 학습 곡선 및 생태계를 기반으로해야합니다. 1) 경력 개발 : Python은 데이터 과학 및 백엔드 개발에 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 적합합니다. 2) 학습 곡선 : Python 구문은 간결하며 초보자에게 적합합니다. JavaScript Syntax는 유연합니다. 3) 생태계 : Python에는 풍부한 과학 컴퓨팅 라이브러리가 있으며 JavaScript는 강력한 프론트 엔드 프레임 워크를 가지고 있습니다.

JavaScript 프레임 워크 : 현대적인 웹 개발 파워JavaScript 프레임 워크 : 현대적인 웹 개발 파워May 02, 2025 am 12:04 AM

JavaScript 프레임 워크의 힘은 개발 단순화, 사용자 경험 및 응용 프로그램 성능을 향상시키는 데 있습니다. 프레임 워크를 선택할 때 : 1. 프로젝트 규모와 복잡성, 2. 팀 경험, 3. 생태계 및 커뮤니티 지원.

JavaScript, C 및 브라우저의 관계JavaScript, C 및 브라우저의 관계May 01, 2025 am 12:06 AM

서론 나는 당신이 이상하다는 것을 알고 있습니다. JavaScript, C 및 Browser는 정확히 무엇을해야합니까? 그들은 관련이없는 것처럼 보이지만 실제로는 현대 웹 개발에서 매우 중요한 역할을합니다. 오늘 우리는이 세 가지 사이의 밀접한 관계에 대해 논의 할 것입니다. 이 기사를 통해 브라우저에서 JavaScript가 어떻게 실행되는지, 브라우저 엔진의 C 역할 및 웹 페이지의 렌더링 및 상호 작용을 유도하기 위해 함께 작동하는 방법을 알게됩니다. 우리는 모두 JavaScript와 브라우저의 관계를 알고 있습니다. JavaScript는 프론트 엔드 개발의 핵심 언어입니다. 브라우저에서 직접 실행되므로 웹 페이지를 생생하고 흥미롭게 만듭니다. 왜 Javascr

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

맨티스BT

맨티스BT

Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

SublimeText3 영어 버전

SublimeText3 영어 버전

권장 사항: Win 버전, 코드 프롬프트 지원!

MinGW - Windows용 미니멀리스트 GNU

MinGW - Windows용 미니멀리스트 GNU

이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

DVWA

DVWA

DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음