PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)

不言
풀어 주다: 2023-04-05 07:56:01
앞으로
8471명이 탐색했습니다.

이 기사는 PHP 인터뷰에서 일반적으로 사용되는 객체지향 지식을 요약한 것입니다(예제 포함). 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

관련 추천 : "2019 PHP 면접 질문 요약(모음) "

이전 기사에 이어 "#🎜 🎜# phpInterview: 객체지향 지식 입문'은 출간 이후에도 계속 업데이트될 예정입니다.

전체 객체 지향 기사의 구조에 관련된 콘텐츠 모듈은 다음과 같습니다.


1. 객체 지향과 프로세스 지향의 차이점은 무엇입니까 ?

2. 객체지향의 특징은 무엇인가요?
3. 생성자와 소멸자는 무엇인가요?
4. 객체지향 범위란 무엇인가요?
5. PHP의 매직 메소드란 무엇인가요?
6. 객체 복제란 무엇인가요?
7. 이게 자기와 부모의 차이점이 뭔가요?
8. 추상 클래스와 인터페이스의 차이점과 연관성은 무엇인가요?
9. 일반적인 PHP 객체지향 면접 질문 설명

PHP 객체지향에 대한 내용은 3개의 기사로 나누어 전체 내용을 설명합니다. 4가지 사항을 중심으로 두 번째 부분에서는 5~8가지 내용을 중심으로 설명하고, 세 번째 부분은 9번째 부분을 중심으로 설명한다.

다음 텍스트의 내용은

"PHP 프로그래머 인터뷰 필기 테스트 가이드" 책에서 가져온 것입니다. 재인쇄할 경우 출처를 남겨주세요: #🎜 🎜#

5. PHP의 매직 메소드란 무엇입니까?

PHP에서는 __(밑줄 2개)로 시작하는 모든 클래스 메소드를 매직 메소드로 유지합니다. 따라서 클래스 메소드를 정의할 때 메소드의 접두어로 __을 사용하는 것은 권장되지 않습니다. 각 매직 메소드의 기능은 다음과 같습니다.

1. __get, __set, __isset, __unset


이 네 가지 메서드는 클래스에 선언되지 않은 속성과 해당 부모 클래스가 설계된 속성을 위한 것입니다.



1) 클래스 속성에 액세스할 때 해당 속성에 액세스할 수 있으면 직접 반환됩니다. , __get 함수를 호출하세요.


메서드 서명은 다음과 같습니다. public mix __get (string $name)

2) 객체의 속성을 설정할 때 해당 속성에 접근할 수 있으면 값을 직접 할당하세요. ; 그렇지 않은 경우 액세스되면 __set 함수가 호출됩니다.

메서드 서명은 다음과 같습니다: public void __set (string $name, Mixed $value)
3) 액세스할 수 없는 속성에 대해 isset() 또는 empty()가 호출되면 __isset()가 호출됩니다.
메서드 서명은 다음과 같습니다: public bool __isset (string $name)
4) 액세스할 수 없는 속성에 대해 unset()이 호출되면 __unset()이 호출됩니다.
메서드 서명은 다음과 같습니다. public bool _unset (string $name)
위의 액세스할 수 없는 속성에는 정의되지 않은 속성이 포함되어 있거나 속성의 액세스 제어가 보호되거나 비공개(액세스 권한이 없는 속성)라는 점에 유의해야 합니다. ) ).
다음은 객체변수를 다른 배열에 저장하는 예입니다.

<?php  
  class Test
  {
     /* 保存未定义的对象变量 */
     private $data = array();
     public function __set($name, $value){
        $this->data[$name] = $value;
     }
     public function __get($name){
        if(array_key_exists($name, $this->data))
            return $this->data[$name];
        return NULL;
     }
     public function __isset($name){
        return isset($this->data[$name]);
     }
     public function __unset($name){
        unset($this->data[$name]);
     }
  }
  $obj = new Test;
  $obj->a = 1;
  echo $obj->a . "\n";
?>
로그인 후 복사

프로그램의 실행 결과는

1

2. __construct, __destruct

1) __construct 생성자, 객체를 인스턴스화할 때 호출됩니다.

2) __destruct 소멸자, 객체가 소멸될 때 호출됩니다. 일반적으로 PHP는 객체가 차지하는 메모리와 관련 리소스만 해제합니다. 프로그래머가 직접 요청한 리소스는 명시적으로 해제해야 합니다. 일반적으로 리소스를 해제해야 하는 작업은 소멸자 메서드에 배치할 수 있습니다. 이렇게 하면 개체가 해제될 때 프로그래머가 적용한 리소스도 해제될 수 있습니다.

예를 들어 생성자에서 파일을 연 다음 소멸자에서 파일을 닫을 수 있습니다.

<?php  
  class Test
  {
     protected $file = NULL;
     function __construct(){
        $this->file = fopen("test","r");
     }
     function __destruct(){
        fclose($this->file);
     }
  }
?>
로그인 후 복사

3. __call() 및 __callStatic()

1) __call( $method, $arg_array ): 이 메서드는 액세스할 수 없는 메서드가 호출될 때 호출됩니다.


2) __callStatic은 __call()과 유사하게 작동합니다. 호출된 정적 메서드가 존재하지 않거나 권한이 충분하지 않은 경우 __callStatic()이 자동으로 호출됩니다.

사용 예는 다음과 같습니다.

    <?php       class Test 
      {
         public function __call ($name, $arguments) {
            echo "调用对象方法 &#39;$name&#39; ". implode(&#39;, &#39;, $arguments). "\n";
    }
         public static function __callStatic ($name, $arguments) {
            echo "调用静态方法 &#39;$name&#39; ". implode(&#39;, &#39;, $arguments). "\n";
         }
      }
      $obj = new Test;
      $obj->method1('参数1');
      Test::method2('参数2');
    ?>
로그인 후 복사

프로그램의 실행 결과는

객체 메서드 'method1' 매개변수 1을 호출합니다.

정적 호출 메소드 'method2' 매개변수 2

4. __sleep() 및 __wakeup()

1) __sleep은 직렬화 중에 호출됩니다.

2) __wakeup은 역직렬화 중에 호출됩니다.

즉, serialize()와 unserialize()를 실행할 때 이 두 함수가 먼저 호출됩니다. 예를 들어 개체를 직렬화할 때 개체에 데이터베이스 연결이 있고 역직렬화 중에 연결 상태를 복원하려는 경우 이 두 메서드를 오버로드하여 이를 수행할 수 있습니다. 샘플 코드는 다음과 같습니다:

<?php   class Test 
  {
     public $conn;
     private $server, $user, $pwd, $db;
     public function __construct($server, $user, $pwd, $db)
     {
         $this->server = $server;
         $this->user = $user;
         $this->pwd = $pwd;
         $this->db = $db;
         $this->connect();
     }
     private function connect()
     {
        $this->conn = mysql_connect($this->server, $this->user, $this->pwd);
        mysql_select_db($this->db, $this->conn);
     }
     public function __sleep()
     {
        return array('server', 'user', 'pwd', 'db');
     }
     public function __wakeup()
     {
        $this->connect();
     }
     public function __destruct(){
        mysql_close($conn);
     }
  }
?>
로그인 후 복사

5. __toString()

__toString 在打印一个对象时被调用,可以在这个方法中实现想要打印的对象的信息,使用示例如下:

<?php   class Test
  {
     public $age;
     public function __toString() {
        return "age:$this->age";
     }
  }
  $obj = new Test();
  $obj->age=20;
  echo $obj;
?>
로그인 후 복사

程序的运行结果为

age:20

6.__invoke()

在引入这个魔术方法后,可以把对象名当作方法直接调用,它会间接调用这个方法,使用示例如下:

<?php   class Test
  {
     public function __invoke()
     {
        print "hello world";
     }
  }
  $obj = new Test;
  $obj();
?>
로그인 후 복사

程序的运行结果为

hello world

7.__set_state()

调用 var_export 时被调用,用__set_state的返回值作为var_export 的返回值。使用示例如下:

<?php   class People
  {
     public $name;
     public $age;
     public static function __set_state ($arr) {
        $obj = new People;
        $obj->name = $arr['name'];
        $obj->age = $arr['aage'];
        return $obj;
     }
  }
  $p = new People;
  $p->age = 20;
  $p->name = 'James';
  var_dump(var_export($p));
?>
로그인 후 복사

程序的运行结果为

People::__set_state(array(
   'name' => 'James',
   'age' => 20,
))  NULL
로그인 후 복사

8.__clone()

这个方法在对象克隆的时候被调用,php提供的__clone()方法对一个对象实例进行浅拷贝,也就是说,对对象内的基本数值类型通过值传递完成拷贝,当对象内部有对象成员变量的时候,最好重写__clone方法来实现对这个对象变量的深拷贝。使用示例如下:

<?php   class People
  {
     public $age;
     public function __toString() {
        return "age:$this->age \n";
     }
  }
  class MyCloneable
  {
     public $people;
     function __clone()
     {
        $this->people = clone $this->people; //实现对象的深拷贝
     }
  }
  $obj1 = new MyCloneable();
  $obj1->people = new People();
  $obj1->people->age=20;
  $obj2 = clone $obj1;
  $obj2->people->age=30;
  echo $obj1->people;
  echo $obj2->people;
?>
로그인 후 복사

程序的运行结果为

age:20 age:30

由此可见,通过对象拷贝后,对其中一个对象值的修改不影响另外一个对象。

9.__autoload()

当实例化一个对象时,如果对应的类不存在,则该方法被调用。这个方法经常的使用方法为:在方法体中根据类名,找出类文件,然后require_one 导入这个文件。由此,就可以成功地创建对象了,使用示例如下:
Test.php:

<?php  
  class Test { 
    function hello() {
        echo &#39;Hello world&#39;;
    }
  }
?>
로그인 후 복사

index.php:

<?php   function __autoload( $class ) {
    $file = $class . &#39;.php&#39;;  
    if ( is_file($file) ) {  
        require_once($file);   //导入文件
    }
  } 

  $obj = new Test();
  $obj->hello();
?>
로그인 후 복사

程序的运行结果为

Hello world

在index.php中,由于没有包含Test.php,在实例化Test对象的时候会自动调用__autoload方法,参数$class的值即为类名Test,这个函数中会把Test.php引进来,由此Test对象可以被正确地实例化。
这种方法的缺点是需要在代码中文件路径做硬编码,当修改文件结构的时候,代码也要跟着修改。另一方面,当多个项目之间需要相互引用代码的时候,每个项目中可能都有自己的__autoload,这样会导致两个__autoload冲突。当然可以把__autoload修改成一个。这会导致代码的可扩展性和可维护性降低。由此从PHP5.1开始引入了spl_autoload,可以通过spl_autoload_register注册多个自定义的autoload方法,使用示例如下:
index.php

<?php   function loadprint( $class ) {
    $file = $class . &#39;.php&#39;;  
    if (is_file($file)) {  
        require_once($file);  
    } 
  } 
spl_autoload_register( &#39;loadprint&#39; );   //注册自定义的autoload方法从而避免冲突
  $obj = new Test();
  $obj->hello();
?>
로그인 후 복사

spl_autoload是_autoload()的默认实现,它会去include_path中寻找$class_name(.php/.inc) 。除了常用的spl_autoload_register外,还有如下几个方法:
1)spl_autoload:_autoload()的默认实现。
2)spl_autoload_call:这个方法会尝试调用所有已经注册的__autoload方法来加载请求的类。
3)spl_autoload_functions:获取所有被注册的__autoload方法。
4)spl_autoload_register:注册__autoload方法。
5)spl_autoload_unregister:注销已经注册的__autoload方法。
6)spl_autoload_extensions:注册并且返回spl_autoload方法使用的默认文件的扩展名。

引申:PHP有哪些魔术常量?

除了魔术变量外,PHP还定义了如下几个常用的魔术常量。
1)__LINE__:返回文件中当前的行号。
2)__FILE__:返回当前文件的完整路径。
3)__FUNCTION__:返回所在函数名字。
4)__CLASS__:返回所在类的名字。
5)__METHOD__:返回所在类方法的名称。与__FUNCTION__不同的是,__METHOD__返回的是“class::function”的形式,而__FUNCTION__返回“function”的形式。
6)__DIR__:返回文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录(PHP 5.3.0中新增)。
7)__NAMESPACE__:返回当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
8)__TRAIT__:返回 Trait 被定义时的名字。Trait 名包括其被声明的作用区域(PHP 5.4.0 新增)。

六、什么是对象克隆?

对于对象而言,PHP用的是引用传递,也就是说,对象间的赋值操作只是赋值了一个引用的值,而不是整个对象的内容,下面通过一个例子来说明引用传递存在的问题:

<?php   
  class My_Class {
    public $color;
  }
  $obj1 = new My_Class ();
  $obj1->color = "Red";
  $obj2 = $obj1;
  $obj2->color ="Blue";     //$obj1->color的值也会变成"Blue"
?>
로그인 후 복사

因为PHP使用的是引用传递,所以在执行$obj2 = $obj1后,$obj1和$obj2都是指向同一个内存区(它们在内存中的关系如下图所示),任何一个对象属性的修改对另外一个对象也是可见的。

PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)

在很多情况下,希望通过一个对象复制出一个一样的但是独立的对象。PHP提供了clone关键字来实现对象的复制。如下例所示:

<?php     
    class My_Class {
      public $color;
    }
    $obj1 = new My_Class ();
    $obj1->color = "Red";
    $obj2 = clone $obj1;
    $obj2->color ="Blue";     //此时$obj1->color的值仍然为"Red"
?>
로그인 후 복사

$obj2 = clone $obj1把obj1的整个内存空间复制了一份存放到新的内存空间,并且让obj2指向这个新的内存空间,通过clone克隆后,它们在内存中的关系如下图所示。

PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)

此时对obj2的修改对obj1是不可见的,因为它们是两个独立的对象。
在学习C++的时候有深拷贝和浅拷贝的概念,显然PHP也存在相同的问题,通过clone关键字克隆出来的对象只是对象的一个浅拷贝,当对象中没有引用变量的时候这种方法是可以正常工作的,但是当对象中也存在引用变量的时候,这种拷贝方式就会有问题,下面通过一个例子来进行说明:

<?php     
    class My_Class {
        public $color;
    }
    $c ="Red";
    $obj1 = new My_Class ();
    $obj1->color =&$c;   //这里用的是引用传递
    $obj2 = clone $obj1;  //克隆一个新的对象
    $obj2->color="Blue";  //这时,$obj1->color的值也变成了"Blue"
?>
로그인 후 복사

在这种情况下,这两个对象在内存中的关系如下图所示。

PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)

从上图中可以看出,虽然obj1与obj2指向的对象占用了独立的内存空间,但是对象的属性color仍然指向一个相同的存储空间,因此当修改了obj2->color的值后,意味着c的值被修改,显然这个修改对obj1也是可见的。这就是一个非常典型的浅拷贝的例子。为了使两个对象完全独立,就需要对对象进行深拷贝。那么如何实现呢,PHP提供了类似于__clone方法(类似于C++的拷贝构造函数)。把需要深拷贝的属性,在这个方法中进行拷贝:使用示例如下:

<?php     class My_Class {
      public $color;
      public function __clone() {
        $this->color = clone $this->color;
      }
    }
    $c ="Red";
    $obj1 = new My_Class ();
    $obj1->color =&$c;   
    $obj2 = clone $obj1;  
    $obj2->color="Blue";  //这时,$obj1->color的值仍然为"Red"
?>
로그인 후 복사

通过深拷贝后,它们在内存中的关系如图1-4所示。

PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)

通过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2完全占用两块独立的存储空间,对obj2的修改对obj1也不可见。

七、this、self和parent的区别是什么?

this、self、parent三个关键字从字面上比较好理解,分别是指这、自己、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。
以下将具体对这三个关键字进行分析。

##1.this关键字##
  1 <?php   2     class UserName {
  3         private $name;    // 定义成员属性
  4         function __construct($name) {
  5             $this->name = $name; // 这里已经使用了this指针
  6         }
  7         // 析构函数
  8         function __destruct() {
  9         }
 10          // 打印用户名成员函数
 11          function printName() {
 12              print ($this->name."\n") ; // 又使用了this指针
 13          }
 14     }
 15     // 实例化对象
 16     $nameObject = new UserName ( "heiyeluren" );
 17     // 执行打印
 18     $nameObject->printName (); // 输出: heiyeluren
 19     // 第二次实例化对象
 20     $nameObject2 = new UserName ( "PHP5" );
 21     // 执行打印
 22     $nameObject2->printName (); // 输出:PHP5
 23 ?>
로그인 후 복사

上例中,分别在5行和12行使用了this指针,那么this到底是指向谁呢?其实,this是在实例化的时候来确定指向谁,例如,第一次实例化对象的时候(16行),当时this就是指向$nameObject 对象,那么执行第12行打印的时候就把print($this->name)变成了print ($nameObject->name),输出"heiyeluren"。
对于第二个实例化对象,print( $this- >name )变成了print( $nameObject2->name ),于是就输出了"PHP5"。
所以,this就是指向当前对象实例的指针,不指向任何其他对象或类。

2.self关键字

先要明确一点,self是指向类本身,也就是self是不指向任何已经实例化的对象,一般self用来访问类中的静态变量。

 1 <?php   2      class Counter {
  3          // 定义属性,包括一个静态变量
  4          private  static  $firstCount = 0;
  5          private  $lastCount;
  6          // 构造函数
  7          function __construct() {
  8              // 使用self来调用静态变量,使用self调用必须使用::(域运算符号)
  9              $this->lastCount = ++ selft::$firstCount;
 10          }
 11          // 打印lastCount数值
 12          function printLastCount() {
 13              print ($this->lastCount) ;
 14          }
 15      }
 16       // 实例化对象
 17      $countObject = new Counter ();
 18      $countObject->printLastCount (); // 输出 1
 19 ?>
로그인 후 복사

上述示例中,在第4行定义了一个静态变量$firstCount,并且初始值为0,那么在第9行的时候调用了这个值,使用的是self来调用,中间使用域运算符“::”来连接,这时候调用的就是类自己定义的静态变量$firstCount,它与下面对象的实例无关,只是与类有关,无法使用this来引用,只能使用 self来引用,因为self是指向类本身,与任何对象实例无关。

3.parent关键字

parent是指向父类的指针,一般使用parent来调用父类的构造函数。

 1 <?php   2       // 基类
  3       class Animal {
  4           // 基类的属性
  5           public $name; // 名字
  6           // 基类的构造函数
  7           public function __construct($name) {
  8              $this->name = $name;
  9           }
 10       }
 11        // 派生类
 12       class Person extends Animal  // Person类继承了Animal类
 13       {
 14           public $personSex; // 性别
 15           public $personAge; // 年龄
 16           // 继承类的构造函数
 17           function __construct($personSex, $personAge) {
 18                parent::__construct ( "heiyeluren" ); // 使用parent调用了父类的构造函数
 19                $this->personSex = $personSex;
 20                $this->personAge = $personAge;
 21           }
 22           function printPerson() {
 23                print ($this->name . " is " . $this->personSex . ",this year " . $this->personAge) ;
 24           }
 25       }
 26       // 实例化Person对象
 27       $personObject = new Person ( "male", "21" );
 28       // 执行打印
 29       $personObject->printPerson (); // 输出:heiyeluren is male,this year 21
 30 ?>
로그인 후 복사

上例中,成员属性都是public的,特别是父类的,是为了供继承类通过this来访问。第18行: parent::__construct( "heiyeluren" ),使用了parent来调用父类的构造函数进行对父类的初始化,因为父类的成员都是public的,于是就能够在继承类中直接使用 this来访问从父类继承的属性。

八、抽象类与接口有什么区别与联系?

抽象类应用的定义如下:

abstract class ClassName{
}
로그인 후 복사

抽象类具有以下特点:
1)定义一些方法,子类必须实现父类所有的抽象方法,只有这样,子类才能被实例化,否则子类还是一个抽象类。
2)抽象类不能被实例化,它的意义在于被扩展。
3)抽象方法不必实现具体的功能,由子类来完成。
4)当子类实现抽象类的方法时,这些方法的访问控制可以和父类中的一样,也可以有更高的可见性,但是不能有更低的可见性。例如,某个抽象方法被声明为protected的,那么子类中实现的方法就应该声明为protected或者public的,而不能声明为private。
5)如果抽象方法有参数,那么子类的实现也必须有相同的参数个数,必须匹配。但有一个例外:子类可以定义一个可选参数(这个可选参数必须要有默认值),即使父类抽象方法的声明里没有这个参数,两者的声明也无冲突。下面通过一个例子来加深理解:

<?php     abstract class A{
        abstract protected function greet($name);
    }
    class B extends A {
        public function greet($name, $how="Hello ") {
            echo $how.$name."\n";
        }
    }
    $b = new B;
    $b->greet("James");
    $b->greet("James","Good morning ");
?>
로그인 후 복사

程序的运行结果为

Hello James
Good morning James
로그인 후 복사

定义抽象类时,通常需要遵循以下规则:
1)一个类只要含有至少一个抽象方法,就必须声明为抽象类。
2)抽象方法不能够含有方法体。
接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在PHP中,接口是通过interface关键字来实现的,与定义一个类类似,唯一不同的是接口中定义的方法都是公有的而且方法都没有方法体。接口中所有的方法都是公有的,此外接口中还可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。要实现一个接口,可以通过关键字implements来完成。实现接口的类中必须实现接口中定义的所有方法。虽然PHP不支持多重继承,但是一个类可以实现多个接口,用逗号来分隔多个接口的名称。下面给出一个接口使用的示例:

<?php   interface Fruit
  {
     const MAX_WEIGHT = 3;   //静态常量
     function setName($name);
     function getName();
  }

  class Banana implements Fruit
  {
     private $name;
     function getName() {
        return $this->name;
     }
     function setName($_name) {
        $this->name = $_name;
     }
  }

  $b = new Banana(); //创建对象
  $b->setName("香蕉");
  echo $b->getName();
  echo "<br>";
  echo Banana::MAX_WEIGHT;   //静态常量
?>
로그인 후 복사

程序的运行结果为

香蕉
 3

接口和抽象类主要有以下区别:
抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类通过关键字abstract来声明。
接口:可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在这种情况下,可以通过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。
二者虽然都是定义了抽象的方法,但是事实上两者区别还是很大的,主要区别如下:
1)对接口的实现是通过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。
2)接口没有数据成员(可以有常量),但是抽象类有数据成员(各种类型的成员变量),抽象类可以实现数据的封装。
3)接口没有构造函数,抽象类可以有构造函数。
4)接口中的方法都是public类型,而抽象类中的方法可以使用private、protected或public来修饰。
5)一个类可以同时实现多个接口,但是只能实现一个抽象类。


위 내용은 PHP 인터뷰에 일반적으로 사용되는 객체지향 지식 요약(예제 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿