文档章节

深入 PHP 面向对象、模式与实践

Yomut
 Yomut
发布于 2016/09/22 17:11
字数 4270
阅读 15
收藏 0

kmzk   

深入php面向对象、模式与实践

1 语法

1.1 基础语法

  1. clone

    需要操作原对象,但又不想影响原对象.

    PHP

     

    1

    $K_back = clone $K;

    基本数据类型和数组都为真复制,即为真副本,当属性为对象时,为假复制,改变副本仍会影响原对象.解决方案:

    PHP

     

    1

    2

    3

    4

    //在原对象中添加

    function __clone(){

        $this->对象 = clone $this->对象

    }

    __clone在clone前自动触发,可以执行一些在备份前的属性操作.

  2. &传递引用

    方法引用传递,改变源对象

    PHP

     

    1

    2

    3

    function set_K(& $K){...}

     

    function & get_K(){...}

     

  3. static延迟静态绑定

    应用场景:Dog类和Person类都需要一个返回实例化的方法,Dog类和Person类都继承于Animal抽象类.

    PHP

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    abstract class Animal{

        public static function create(){

            //实例化调用类

            return new static();

        }

    }

     

    class Person extends Animal{...}

     

    //返回Person实例化类

    Person::create();

     

  4. 拦截器
    1. __get($property),访问未定义的属性时调用.
    2. __set($property,$value),给未定义的属性赋值时被调用.
    3. __isset($property),对未定义属性调用isset()方法时调用.
    4. __unset($property),对未定义属性调用unset()方法时调用.
    5. __call($method,$arg_array),调用未定义方法时调用.

      __call很有用,但要慎用,因为太灵活.

      应用场景:有一个专门打印Person类信息的Person_Writer类,如果通过Person类调用Person_Writer类.

      PHP

       

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      //Person委托Person_Writer类处理打印事务.

      class Person {

          private $writer;

          ...

       

          function __call($method_name,$args){

              if(methood_exists($this->wirter,$method_name)){

                  return $this->writer->$method_name($this);

              }

          }

       

          //高级__call写法,当委托方法参数不确定时使用.

          function __call($method_name,$args){

              //当然这里这样写法意义不大,但是call一般都是用call_user_func_array调用

              $args = $this ;

              if(methood_exists($this->wirter,$method_name)){

                  return call_user_func_array(

                      array($this->writer,$method_name),$args);

                  )

              }

          }

       

      }

       

  5. 回调函数

    应用场景: 3个类,Product类,Product_Sale类,Product_Totalizer类,要实现:当卖出Product总共价格超过指定金额时,输出警告.

    PHP

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    //Product

    class Product {

        public $name;

        public $price;

    }

     

    //Product_Sale

    class Product_Sale {

        private $callbacks;

     

        //记录回调函数

        function register_callback ($callback) {

            if(! is_callback($callback)){

                thow new Exception('callback not callable');

            }

            $this->callbacks[] = $callback;

        }

     

        //执行回调函数

        function sale ($product){

            print "{$product->name} : 处理中 n";

            foreach($this->callbacks as $callback){

                call_user_func($callback , $product);

            }

        }

    }

     

    //Produce_Totalizer

    class Produce_Totalizer {

        static function warn_amount ($amt) {

            $count = 0;

            return function ($produce) use ($amt , &count) {

                $count += $produce->price;

                print " count : {count}n"

                if($count>$amt){

                    print "超过指定金额{$amt}啦~";

                }

            };

        }

    }

     

    //模拟场景

    $product_sale = new Produce_Sale();

    //指定报警金额为8块

    $product_sale = register_callback(Produce_Totalizer::warn_amount(8));

     

    //卖商品

    $product_sale->sale(new Product("Durex",6));

    $product_sale->sale(new Produce("Jissbon",5));

     

    //输出结果

    Durex : 处理中

        count :6

     

    Jissbon : 处理中

        count: 11

     

    超过指定金额8块啦~

     

  6. get_class()instanceof

    get_class(类)用于判断是否精准等于类名;

    instanceof 可以判断是否其本身或继承于某父类.

  7. 类中的方法和类中的属性

    get_class_methods('类名'):获取类中所有方法.

    get_class_vars('类名'):获取类中所有public参数;

  8. 反射API

2 模式

2.1 组合

问题:课堂类被演讲类和研讨会类继承着.但是演讲类和研讨类都要实现一次性计费和上N次课计费的方法.和输出计算的方式.

解决方案1: 在课堂类中添加计算一次性付费的方法,上N次课的计费方法和输出计算方式的方法.

解决方案2: 运用组合,将处理计费和输出计算方式单独封装为一个计费策略类.
组合模式

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

abstract class Cost_Strategy {

    protected $duration;

    abstract function cost ();

    abstract function charge_type();

 

    public __construct($duration){

        $this->duration = $duration;

    }

}

 

class Timed_Const_Strategy extends Cost_Stratedy {

    function cost () {

        //上一次课给5块钱- -.

        return $this->duration * 5;

    }

 

    function charge_type(){

        return "多次课结算";

    }

}

 

class Fixed_Const_Strategy extends Cost_Stratedy {

    function cost (){

        return 30 ;

    }

 

    function charge_type(){

        return "一次性课结算";

    }

}

 

abstract class Leason {

 

    private $cost_strategy;

 

    public __construct(Const_Strategy $cost_strategy){

        $this->cost_strategy = $cost_strategy;

    }

 

    function __call($method_name,$args){

        $args = $cost_strategy ;

        if(methood_exists($this->cost_strategy,$method_name)){

            return call_user_func_array(

                array($this->writer,$method_name),$args);

            )

        }

    }

}

 

//运用

$leasons[] = new Seminar(new Timed_Const_Strategy(4));

$leasons[] = new Lecture(new Fixed_Const_Strategy(null));

 

foreach ($leasons as $leason){

    print "leason charge : {$leason->const()}";

    print "charge_type : {$leason->charge_type()}"

}

 

leason charge 20. charge_type : 多次课结算;

leason charge 30. charge_type : 一次课结算;

组合既委托.同级委托.

继承既父子关系.

3 生成对象

3.1 单例模式

确保系统中只有唯一一个用例.例如系统配置文件.

重点

1: 构造方法私有.

2: 类本身包含自己的实例化属性.

单例模式

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Preferences {

    private static $instance;

    private function __construct(){ ... }

 

    public static function get_instance(){

        if(empty(self::$instance)){

            self::$instance = new Preferences();

        }

        return self::$instance;

    }

    ...

}

 

//使用

$preferences = Preferences::get_instance();

 

3.2 工厂模式

通过一个父类,生产处多个不同功能的子类.

特点:产品方(新浪微博)和需求方(显示新浪微博)一一对应.

问题:印象笔记中,来源可能为新浪微博,或者开发者头条,在印象笔记显示的时候,两者的页眉和页尾是不一样的.

工厂模式

3.3 抽象模式

RLGL!!!.印象笔记不只要显示新浪微博内容!!!还要显示我的新浪账号,还要该微博啊!!卧槽~憋着急,吻我.

工厂模式主要用于生产一一对应的产品方和需求方,而抽象模式要做的是一个需求方(印象笔记_显示新浪微博),要多个工厂(把需求方抽象为多个需求方),例如提供新浪内容的工厂,提供新浪账号的工厂.提供微博内容的评论的工厂等.

抽象模式

代码:

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

abstract class Show_Evernote {

    abstract function get_header_text();

    abstract function get_context();

    abstract function get_footer_text();

    abstract function get_user();

    abstract function get_comment();

 

}

 

class 显示新浪微博 extends Show_Evernote{

    function get_header_text(){...};

    function get_context(){new 新浪微博_内容;}

    function get_footer_text(){...};

    function get_user(){new 新浪微博_账号 ;}

    function get_comment(){new 新浪微博_评论;}

}

 

//使用

印象笔记控件类->内容 = 显示新浪微博->get_context;

印象笔记控件类->账号 = 显示新浪微博->get_context;

...

 

3.4 平行模式

当使用工厂/抽象模式必须要制定具体的创建者(需求方).

平行模式和抽象模式的模型图一致,但代码实现不一样.

抽象模式中父类均为抽象类,而平行模式中,所以类都为普通类,方便父类的实例化.

在这里列出显示印象笔记类的实现代码

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

class Show_Evernote{

    private $内容;

    private $账号;

    private $评论;

 

    function __construct(内容,账号,评论){

        $this->内容 = 内容;

        $this->账号 = 账号;

        $this->评论 = 评论;

    }

 

    function get_内容(){

        return clone $this->内容);

    }

 

    function get_账号(){

        return clone $this->账号);

    }

 

    function get_评论(){

        return clone $this->评论;

    }

}

 

//使用

$factory = new Show_Evernote(

    new 新浪微博内容(),

    new 新浪微博账号(),

    new 新浪微博评论()

);

 

印象笔记控件类->显示印象笔记 = $factory;

其实大家可以发现,原型模式只不过只在最顶层类中包装了一下各组件子类而已,然而这样可以轻松的组合他们,例如实现一个显示新浪微博内容,但要显示开发者头条账号的需求?

4 使用对象

4.1 组合模式

组合模式,可以理解为单一对象管理组合对象(聚合组件),最终组合体下的各个组合部件最好类型一致.不然特殊性越多,需要判断就越多.

假设捶背男,洗脚男,洗发男,用来服务一个人(妹子).

假设妹子的几个部位可用的服务男均为无限个.

组合模式

PHP

 

1

2

3

4

5

6

7

8

9

//创建一个妹子

$妹子 = new 人();

 

//添加洗脚男、捶背男

$妹子->add_man(new 洗脚男);

$妹子->add_man(new 捶背男);

 

//循环所有男的给予舒服的方法.

$妹子->计算舒服程度();

这是一个很理想的组合模式,在现实情况,我们使用组合模式,可能不得不创建多种类型的洗脚男,需要添加许多判断条件.

4.2 装饰模式

装饰模式,首先洗脚男,洗发男,捶背男都是人,但是如果,一个男的又捶背,又洗发,这怎么玩?.add_man两次?这不科学吧,来给这些男的装饰一下吧~

装饰模式

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

abstract class 人{

    ...

    abstract function get_well();

}  

 

class 男 extends 人 {

    //无论你是神马男,服务你,你就能获得10点舒服度.

    private $well = 10;

    function get_well(){

        return $this->well();

    }

}

 

abstract class 装饰男类型 extends 人 {

    protected $人;

    function __construct(人 $人){

        $this->人 = $人;

    }

}

 

class 捶背装饰 extends 类型男装饰{

    function get_well(){

        return $this->人->get_well()+30;

    }

}

 

class 洗发装饰 extends 类型男装饰{

    function get_well(){

        return $this->人->get_well()+20;

    }

}

 

class 洗褪装饰 extends 类型男装饰{

    //老子不喜欢别人碰我的毛裤.

    function get_well(){

        return $this->人->get_well()-20;

    }

}

 

//创建捶背,能给予的舒服指数 - -嘻嘻.

$人 = new 捶背装饰(new 男);

$人->get_well(); // 10+30 = 40

 

//来来来,全能选手,捶背、洗发、洗腿一起来

$人 = new 洗脚装饰(new 洗发装饰(new 捶背装饰(new 男()))); //10+30+20-20 = 40,注意顺序,由里到外执行.

装饰模式,既(组合+继承),基类方法一定要尽量少,不然子类可能有它不该有的方法.直接类继承,她只可能是一种形态,而她的多种形态可能一并拥有的时候,应该运用组合.

继承即单一多态,组合既多种多态.

这个例子中,你可以添加女,然后把装饰男类型改为装饰通用类型,但每个get_well()都要多一个判断是男还是女(如果给予的舒服程度不一样).

这只是确保不可能出现在,之外的第三种人,如果基类为动物,给予服务的可能是鸡,鹅,鸭,那么装饰类型应该运用工厂模式,动物形态和装饰形态一一对应.方便拓展.

除了服务类型,服务男的样子也很重要,这就多了一种装饰,现在有装饰男类型相貌男类型,这种情况怎么破,其实类似.

两个装饰

PHP

 

1

2

//如何获取捶背的帅哥麦?,

$人 =new 男类型(new 捶背(new 帅哥麦(new 男())));

 

4.3 外观模式

即给外部系统提供清晰接口

例如当Model层写得很混乱,但是里面的方法还能用,那我们的Controller层应该列举一些清晰的访问方法来供View层访问.外观模式,强调的是清晰的访问接口.

5 执行任务

5.1 策略模式

给类添加功能.对象要显式的调用它.

继续刚才的洗脚男和人的故事吧…你丫的爽完了要给钱吧?支付宝?微信?现金?

这个付款方式有多种,实现方法不应该放在类中,而是应该委托给别的类

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

abstract class 人 {

 

    protectd $支付方式;

 

    function set_支付方式(){...}

 

    function 付款(金额){

        return $支付方式->付款($金额);

    }

}

 

abstract class 付款{

    abstract function 付款($金额);

}

 

class 支付宝付款 extends 付款{

 

    function 付款($金额){

        return 外接支付宝付款流程($金额);

    }

}

...

 

//使用

$男 =new 男();

 

///爽爽爽

...

 

//结账

$支付宝支付账单 = new 支付宝付款($金额);

$人 = new 男();

$人->set_支付方式(new 支付宝付款());

$人->付款();

 

5.2 观察者模式

当被观察者发生变化,观察者需要被通知.

当数据发生变化,页面需要被通知.

使用步骤:

  1. 观察者加载到被观察者中.
  2. 被观察者通知观察者.

观察者模式

例如登陆类(被观察)状态改变,要出发邮件系统和日志系统(观察者)

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

interface 被观察者{

    function attach(观察者);

    function detatch(观察者);

    function notify();

}

 

class Login implements 被观察者{

    private $观察者;

 

    function __construct(){

        $this->观察者 = array();

    }

 

    function attach($观察者){

        $this->观察者 = $观察者;

    }

 

    function detach($观察者){

        //删除某个观察者的操作;

    }

 

    function notify(){

        foreach ($this->观察者 as $单个观察者){

            $单个观察者->update($this);

        }

    }      

}

 

interface 观察者{

    function update(被观察者);

}

 

abstract class Login_观察者 implements 观察者{

    private $login;

    function __construct (Login $login){

        $this->login = $login;

        $login->attach($this);

    }

 

    function update(观察者 $观察者){

        if ($观察者 ===$this->login){

            $this->do_update($观察者);

        }

    }

    abstract function do_update(Login $login);

}

 

class 邮件观察者 extends 登陆观察者 {

    function do_update(Login $login){

        //判断条件 发送邮件

    }

}

 

class 日志观察者 extends 登陆观察者 {

    function do_update(Login $login){

        //判断条件 记录到日志;

    }

}

 

//使用

$login = new Login();

new 邮件观察者 ($login);

new 日志观察者 ($login);

PHP有内置的SPL实现上述的观察者模式.

5.3 访问者模式

问题: 在一个军队中,有很多军队,军队下面可能包含军队/步兵/弓箭手,这时我们要显示一个军队的战斗力/需要粮食的各级分配?(遍历对象并设置显示方法).怎么办?.解决办法是军队还是保存自己的基本信息,设置一个访问者,访问者包含总战斗力方法和总粮食的方法.

访问者模式

访问者

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

abstract class 军队访问者{

    abstract function 访问(单元);

 

    function 访问军队($军队){

         $this->访问($军队);

    }

    function 访问弓箭手($弓箭手){

        $this->访问($弓箭手);

    }

 

    //这里重复定义了大量代码,其实可以用call来替代

    function __call($method_name,$args){

        if(strrpos($method_name, "访问")){

            return call_user_func_array(

                array($this,"访问"),$args

            );

        }

    }

}

 

class 军队战斗力访问者 extends 军队访问者{

    private $text="";

 

    function 访问($单元){

        $ret = "";

        $pad = 4*$单元->getDpth(); //设置显示深一级前面多4个空格.

        $ret .= sprintf( "%{$pad}s","");

        $ret .= get_class($单元). ": ";

        $ret .= "战斗力: " .$单元->bombardStrenth()."n";

        $this->text .=$ret;

    }

 

    function get_text(){

        return $this->text;

    }

}

被访问者

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

abstract class 单元{

    function 接受($军队访问者){

        $method = "访问_".get_class($this);

        $军队访问者->$method($this);

    }

 

    private $depth;

    protected function set_depath($depth){

        $this->depth=$depth;

    }

 

    function get_depth(){

        return $this->depth;

    }

    ...

}

 

abstract class 综合单元 extends 单元{

    function 接受($军队访问者){

        parent::接受($军队访问者)

        foreach($this->单元集合 as $this_unit){

            $this->unit->接受($军队访问者);

        }

    }

}

 

class 军队 extends 综合单元{

    function bombardStrenth(){

        $ret =0;

        foreach($this-units() as $unit){

            $ret += $unit->bombardStrenth();

        }

        return $ret

    }

}

 

class 弓箭手 extends 单元{

    function bombardStrenth(){

        return 4;

    }

}

调用

PHP

 

1

2

3

4

5

6

7

$main_army = new Army();

$main_army->add_unit(new 步兵());

$main_army->add_unit(new 弓箭手());

 

$军队战斗力访问者_实例 =new 军队战斗力访问者();

$main_army->接受(均分战斗力访问者);

print $军队战斗力访问者->get_text();

输出

 

1

2

3

军队: 战斗力: 50

    步兵: 攻击力 :48

    弓箭手: 攻击力: 4

5.4 命令模式

例子为Web页面的login和feed_back,假如都需要使用ajax提交,那么问题来了,将表单封装好提交上去,得到了返回结果.如何根据返回结果跳转不同的页面?.

有些同学就说了,login和feed_back各自写一个方法憋,提交的时候调用各自的方法.

然后再来个logout命令..增加..删除..命令怎么办..

命令模式比较适合命令执行例如登陆,反馈等简单只需要判断是否成功的任务

命令模式

命令:

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

abstract class Command{

    abstract function execute(Conmmand_Context $context);

}

 

class Login_Command extends Command{

    function execute(CommandContext $context){

        $managr =Register::getAccessManager();

        $user = $context->get("username");

        $pass = $context->get('pass');

        $user_obj = $manager->login($user,$pass);

        if(is_null($user_obj)){

            $context->setError($manager->getError());

            return false;

        }

        $context->addParam("user",$user_obj);

        return true;

    }

}

部署命令的调用者

PHP

 

1

2

3

4

5

6

7

8

class Command_Facotry{

    public function get_command($action){

        $class = UCFirst(strtolower($action))."_Command";

        $cmd = new $class();

        return $cmd;

    }

 

}

客户端

PHP

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Controller{

    private $context;

    function __construct(){

        //Command_Context主要用来存储request和params

        $this->context =new Command_Context();

    }

    function process(){

        $cmd Command_Factory::get_commad($this->context->get('action'));

        if(!$cmd-execute($this->context)){

            //错误处理

        }else{

            //成功 分发视图

        }

    }

}

使用

PHP

 

1

2

3

4

5

6

$controller =new Controller();

$context = $controller->get_context();

$context->add_param('action','login');

$context->add_param('username','404_k');

$context->add_param('pass','123456');

$controller->process();

© 著作权归作者所有

Yomut
粉丝 19
博文 191
码字总数 167789
作品 0
厦门
后端工程师
私信 提问
PHP中,在父类对象中缓存子类的结果

在一本PHP的书中《深入PHP 面向对象,模式与实践》, 书的第164面,第三段中文中,有一句话, 解决问题的一个办法是在父级对象中缓存计算结果 我想问的是,这句话是什么意思? 大家如果手上有...

包菜兄
2013/03/23
297
2
PHP职业规划

PHP职业规划 初级PHP程序员 需要掌握知识点: HTML 熟练 CSS 熟练 Javascript 基础 jQuery 基础 SQL 基础 PHP 熟练 职业要求 能够独立进行网页制作与排版 能够制作简单的前端效果 或 将前端效...

染墨青衿
2014/12/22
6
0
PHP学习笔记——延迟静态绑定

使用场景 先来观察以下代码: 输出: object(aClass)#1 (0) { } object(bClass)#1 (0) { } 以上aClass和bClass继承于base这个抽象类,但是在两个子类中同时实现了create()这个静态方法。遵从o...

botkenni
2016/11/07
4
0
大家看过什么技术书呢?来分享一下心得吧。

趁光棍节折扣优惠和对JS有一个更好的理解,跑去买了一本 《javascirp权威指南》第六版 83块钱啊。。。 回头看看自己买过的书。有以下书本: 1.《php和mysql web开发》 这本是我入门PHP的书。...

lazyphp
2013/11/11
502
11
程序员需要看的书籍

程序员要看的书籍 预备书籍 《如何阅读一本书》作者是莫提默•J.艾德勒 《专注力》作者是埃伦•兰格 《暗时间》 《番茄时钟工作法》 《集异壁》 基础书籍 《我的第一本英语语法书》 《程序员...

盖世英雄到来
2016/01/06
248
0

没有更多内容

加载失败,请刷新页面

加载更多

mysql-connector-java升级到8.0后保存时间到数据库出现了时差

在一个新项目中用到了新版的mysql jdbc 驱动 <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.18</version> ......

ValSong
今天
5
0
Spring Boot 如何部署到 Linux 中的服务

打包完成后的 Spring Boot 程序如何部署到 Linux 上的服务? 你可以参考官方的有关部署 Spring Boot 为 Linux 服务的文档。 文档链接如下: https://docs.ossez.com/spring-boot-docs/docs/r...

honeymoose
今天
6
0
Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用

1. 前言 生产上对 Web 应用 的监控是十分必要的。我们可以近乎实时来对应用的健康、性能等其他指标进行监控来及时应对一些突发情况。避免一些故障的发生。对于 Spring Boot 应用来说我们可以...

码农小胖哥
今天
9
0
ZetCode 教程翻译计划正式启动 | ApacheCN

原文:ZetCode 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远。 ApacheCN 学习资源 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 ...

ApacheCN_飞龙
今天
5
0
CSS定位

CSS定位 relative相对定位 absolute绝对定位 fixed和sticky及zIndex relative相对定位 position特性:css position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left属性则...

studywin
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部