Orientado a objetos - um trampolim para o estágio avançado do JavaScript

prefácio

Na programação de computadores, objeto é um conceito muito importante. Ele pode nos ajudar a organizar e gerenciar melhor os dados e tornar o código mais modular e reutilizável. Um objeto pode armazenar vários pares chave-valor, cada par chave-valor representa um atributo e o atributo pode ser um valor de um tipo básico ou outro objeto. Por meio de objetos, podemos definir e chamar métodos, operar propriedades ou implementar funções específicas.

A ideia da programação orientada a objetos enfatiza a organização e o gerenciamento do código por meio de objetos e classes, facilitando sua compreensão e manutenção. De fato, decompor problemas e soluções em objetos é uma forma muito natural e intuitiva, e também é a chave para alcançar a reutilização e modularização do código.

Nos estudos seguintes, vamos entender e dominar esses conceitos mais profundamente, praticar e consolidar o conhecimento que aprendemos através de pequenos casos. Vamos embarcar em uma jornada de aprendizado juntos!

1. Reconheça o objeto

1. O que é um objeto

  • Conceito: Objeto (objeto) é uma coleção de "pares chave-valor" , representando o relacionamento de mapeamento entre atributos e valores
    insira a descrição da imagem aqui

  • Sintaxe: k e v são separados por dois pontos , cada grupo de k:v é separado por vírgulas e vírgulas não são necessárias após o último par k:v

    • Se o nome da chave de propriedade do objeto não estiver em conformidade com a convenção de nomenclatura do identificador JS, o nome da chave deverá ser colocado entre aspas
    • Por exemplo: 'favorite-book':'舒克和贝塔'há um traço no nome do atributo, que não está de acordo com a convenção de nomenclatura do identificador ]S, e o nome do atributo deve estar entre aspas
  • Acesso às propriedades do objeto

    • Você pode usar " sintaxe de ponto " para acessar o valor da chave especificada no objeto

      xiaoming.name;//小明
      xiaoming.age;//12
      xiaoming.hobbys;//['足球',游泳','编程]
      
    • Se o nome do atributo não estiver em conformidade com a convenção de nomenclatura do identificador ]S, ele deve ser acessado com colchetes

      xiaoming['favorite-book'];//舒克和贝塔
      
    • Se o nome do atributo for armazenado como uma variável, ele deve estar entre colchetes

      var obj = {
              
              
      	a:1,
      	b:2,
      	C:3
      };
      //属性名用变量存储
      var key ='b';
      console.log(obj.key); //undefined
      console.log(obj[key]);//2
      
  • mudança de propriedade

    • Use diretamente o operador de atribuição para reatribuir uma propriedade para alterar a propriedade
      var obj ={
              
              
      	a:10
      }
      obj.a = 30;  //将obj里a的值改成了30
      ob].a++;    // a = 31
      
  • criação de atributos

    • Se o próprio objeto não tiver um valor de propriedade, a propriedade será criada quando a sintaxe de ponto for usada para atribuir um valor
      var obj ={
              
              
      	a:10
      };
      obj.b = 40;  //在obj对象中创建了b:40这个键值对
      
  • exclusão de propriedade

    • Se você deseja excluir uma propriedade de um objeto, você precisa usar o operador delete

      var obj = {
              
              
      	a:1,
      	b:2
      };
      delete obj.a;
      

2. Métodos de objeto

  • Se o valor de uma propriedade for uma função , também é chamado de " método " do objeto

    varxiaoming = {
          
          
    	sayHello:function(){
          
               //sayHello方法
    	 	console.log('你好,我是小明,今年12岁,我是个男生');
    	 	}
    }
    
  • Chamando uma função com o método dotxiaoming.sayHello();

  • O método também é uma função , mas o método é o "atributo de função" do objeto e precisa ser chamado pelo objeto

3. Travessia de objeto

  • Com o loop for ... in ... , você pode iterar sobre cada chave do objeto
    insira a descrição da imagem aqui

4. Clonagem profunda e superficial de objetos

  • Quando o objeto é um valor de tipo de referência
    • Um objeto não pode ser var obj1 = obj2clonado usando uma sintaxe como esta
    • Ao usar == ou === para comparar objetos, a comparação é se eles são o mesmo objeto na memória , não se os valores de comparação são os mesmos
  • clone raso
    • A clonagem superficial de objetos pode ser obtida usando o ciclo for ...in...
  • clone profundo
    • Semelhante ao tipo de array, a clonagem profunda de objetos requer o uso de recursão

         var obj1 = {
              
              
            a:1,
            b:2,
            c:[33,44,{
              
              
              m:55,
              n:66,
              p:[77,88]
            }]
          };
          // 定义一个深克隆函数
          function  deepClone(o){
              
              
            // 判断o是对象还是数组,必须先判断是不是数组,因为数组的类型也是对象
            if(Array.isArray(o)){
              
              
                //数组
              var result = [];
              for(var i=0;i < o.length;i++){
              
              
                result.push(deepClone(o[i]));
              }
            }else if(typeof o == 'object'){
              
              
                // 对象
              var result = {
              
              };
              for (var k in o){
              
              
                result[k] = deepClone(o[k])
              }
            }else{
              
              
                //基本类型值
              var result = o;
            }
            return result;
          }
          var obj2 = deepClone(obj1);
          console.log(obj2);
          
          // 测试一下
          console.log(obj1 === obj2); //false
          
          obj1.c.push(99);
          console.log(obj2);  //obj2是不变的,因为没有'藕断丝连'的现象
        
      

2. Conheça o contexto da função

  • O contexto da função ( esta palavra-chave) é determinado pelo método de chamada ; a mesma função é chamada de formas diferentes e o contexto da função é diferente

    var xiaoming = {
          
          
    	nickname:小明,
    	age:12,
    	sayHello = function (){
          
          
    		console..log('我是'+this.nickname+',我'+this.age+'岁了)}
    };
    
    • Cenário 1: A função é chamada pelo objeto ponto, e este na função refere-se ao objeto pontilhado

      xiaoming.sayHello();
      
    • Caso 2: Os parênteses chamam diretamente a função, e isso na função refere-se ao objeto janela

      var sayHello = xiaoming.sayHello;
      sayHello();
      
  • função é a estratégia " contexto de tempo de execução "

  • Se a função não for chamada, o contexto da função não pode ser determinado

1. Regras de contexto para funções★

insira a descrição da imagem aqui

  • Regra①

    • O objeto ponto chama sua função de método, então o contexto da função é o objeto pontilhado
    • 对象.方法();
  • Regra ②

    • Os parênteses chamam a função diretamente e o contexto da função é o objeto da janela
    • 函数();
  • Regra ③

    • A matriz (objeto semelhante a uma matriz) enumera a função a ser chamada e o contexto é esta matriz (objeto semelhante a uma matriz)
      • O que é um objeto semelhante a uma matriz: um objeto cujo nome de chave é uma sequência de números naturais (começando em 0) e possui um atributo de comprimento
      • O objeto de argumentos é o objeto semelhante a uma matriz mais comum, que é a lista de parâmetros real da função
    • 数组[下标]();
  • Regra ④

    • Função no IIFE, o contexto é o objeto da janela

      (function(){
              
              
      
      })();
      
  • Regra ⑤

    • O temporizador e o retardador chamam a função, o contexto é o objeto da janela
    • setInterval(函数,时间);
    • setTimeout(函数,时间);
  • Regra ⑥

    • O contexto do manipulador de eventos é o elemento DOM ao qual o evento está vinculado
      DOM元素.onclick=function(){
              
              
      }
      

2. ligue e candidate-se ★

  • call e apply podem especificar o contexto da função
  • 函数.ca11(上下文);Para listar parâmetros com vírgulas
  • 函数.apply(上下文);Para escrever os parâmetros no array

3. Construtor

1. Chame a função com o novo operador

  • "Quatro Passos"

    • 1) Um objeto em branco é criado automaticamente no corpo da função
    • 2) O contexto (this) da função apontará para este objeto
    • 3) A instrução no corpo da função será executada
    • 4) A função retornará automaticamente o objeto de contexto, mesmo que a função não tenha uma instrução de retorno

2. Classes e instâncias

insira a descrição da imagem aqui

3. Construtores e 'classes'

  • Java, C++, etc. são linguagens " orientadas a objetos " (orientadas a objetos)

  • JavaScript é uma linguagem " baseada em objeto"

  • O construtor em JavaScript pode ser comparado à "classe" na linguagem OO . O método de escrita é realmente semelhante, mas ainda é fundamentalmente diferente da linguagem OO real.

4. Protótipo e cadeia de protótipos

1. Protótipo e busca em cadeia de protótipos★

  • Qualquer função tem um atributo protótipo , e protótipo significa "protótipo"
  • O valor da propriedade protótipo é um objeto, que por padrão tem a propriedade construtor apontando de volta para a função
    insira a descrição da imagem aqui
  • O atributo protótipo para funções comuns é inútil, mas o atributo protótipo para construtores é muito útil
    • A propriedade protótipo de um construtor é o protótipo de sua instância
      insira a descrição da imagem aqui
            console.log(xiaoming.__proto__ === People.prototype); //true 
    
  • pesquisa de cadeia de protótipo
    • JavaScript estipula que uma instância pode acessar as propriedades e métodos de seu protótipo
  • método hasOwnProperty()
    • Pode verificar se o objeto realmente "possui" uma propriedade ou método
  • no operador
    • Você só pode verificar se uma determinada propriedade ou método pode ser acessado pelo objeto, você não pode verificar se é sua própria propriedade ou método

2. Adicione métodos no protótipo

  • Desvantagens de adicionar métodos diretamente às instâncias: cada instância e a função de método de cada instância são funções diferentes na memória, resultando em desperdício de memória
  • Solução: Adicione o método ao protótipo
    insira a descrição da imagem aqui

3. O fim da cadeia do protótipo

  • O fim da cadeia de protótipos de todas as coisas - Object.prototype

O caminho geral da cadeia de protótipo

  • exemplo de código

      function People(){
          
          
    
      }
      var xiaoming = new People();
      console.log(xiaoming.__proto__.__proto__ === Object.prototype);	//true
      console.log(Object.prototype.__proto__);							 //null
    
      console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));	 //true
    
    
  • cadeia de protótipo do array

    insira a descrição da imagem aqui

 var arr = [22,33,4,555];

  console.log(arr.__proto__ === Array.prototype);            //true
  console.log(arr.__proto__ .__proto__=== Object.prototype); //true
  console.log(Array.prototype.hasOwnProperty('push'));        //true

4. Herança★

  • A herança descreve o relacionamento "é um tipo de" entre duas classes . Por exemplo, um aluno "é um tipo de" pessoa, portanto, um relacionamento de herança é formado entre um ser humano e uma classe de aluno

  • Pessoas é a " classe pai " (ou "superclasse", "classe base"); Aluno é a " subclasse " (ou "classe derivada")

  • A subclasse enriquece a classe pai, tornando a descrição da classe mais específica e detalhada

  • exemplo

  • Herança através da cadeia de protótipos
    • Deixe o protótipo do construtor da subclasse apontar para uma instância da classe pai:Student.prototype = new People();

insira a descrição da imagem aqui

		 //父类:人类
		  function People(name,age,sex){
    
    
		    this.name = name;
		    this.age = age;
		    this.sex = sex;
		  }
		  People.prototype.sayHello = function (){
    
    
		    console.log('你好,我是'+this.name +'我今年'+this.age+'岁了');
		  }
		  People.prototype.sleep = function (){
    
    
		    console.log(this.nam+'开始睡觉,zzzz');
		  }
		// 子类:学生
		  function Student(name,age,sex,school,studentNumber){
    
    
		    this.name = name;
		    this.age = age;
		    this.sex = sex;
		    this.school = school;
		    this.studentNumber = studentNumber;
		  }
		  //关键语句,实现继承
		  Student.prototype = new People();
		  Student.prototype.study = function (){
    
    
		    console.log(this.name + '正在学习');
		  }
		  Student.prototype.exam = function (){
    
    
		    console.log(this.name+'正在考试,加油!');
		  }
		
		  //实例化
		  var hanmeimei = new Student('韩梅梅',12,'女','CSDN大学',123456);
		  hanmeimei.study();
		  hanmeimei.sayHello();

Cinco, subindo para orientação a objetos

  • A essência da orientação a objetos: defina classes diferentes, deixe as instâncias da classe funcionarem
  • Vantagens da orientação a objetos: escrita de programa mais clara, estrutura de código mais compacta, código mais robusto e manutenção mais fácil
  • Ocasiões em que a orientação a objetos é frequentemente usada: ocasiões que requerem encapsulamento e reutilização (pensamento de componentes)

1. Caixa pequena de semáforos

  • Usando a programação orientada a objetos, você pode resolver o problema de um grande número de semáforos conflitantes com o pensamento " componente "

  • Programação orientada a objetos, o mais importante é escrever classes

  • classe TrafficLight

    • Atributos: própria cor atual, próprio elemento DOM dom
    • Método: inicializar init(), mudar a cor changeColor(), ligar o evento bindEvent()
  • Exemplo de código:

     #box img{
          
          
                width: 80px ;
            }
    
    <div id="box" ></div>
    
    //定义红绿灯类,构造函数
            function TrafficLight(){
          
          
                //颜色属性,一开始都是红色
                //红色1,黄色2,绿色3
                this.color = 1;
                //调用自己的初始化方法
                this.init();
                //绑定监听
                this.bindEvent();
            }
            //初始化方法
            TrafficLight.prototype.init = function (){
          
          
                // alert('我是init方法');
                //创建自己的DOM
                this.dom = document.createElement('img');
                this.dom.src = this.color+'.jpg';
                box.appendChild(this.dom);
            }
            // 绑定监听
            TrafficLight.prototype.bindEvent = function (){
          
          
                //备份上下文,这里的this指的是JS实例
                var self = this;
                //当自己的dom被点击时
                this.dom.onclick = function (){
          
          
                    // 当被点击时,调用自己的changeColor方法
                    self.changeColor();
                };
            }
            // 改变颜色
            TrafficLight.prototype.changeColor = function (){
          
          
                // 改变自己的color属性,从而有一种"自治"的感觉,自己管理自己不干扰别的红绿灯
                this.color++;
                if(this.color == 4){
          
          
                    this.color = 1;
                }
                // 光color属性变化没用,还要更改自己的dom中src属性,才能更换图片
                this.dom.src = this.color+'.jpg';
            };
    
    
    
            // 得到盒子
            var box = document.getElementById('box');
    
            // 实例化100个
            var count = 100;
    
            // 当count-- 为0的时候,判断为false,跳出循环
            while(count--){
          
          
                new TrafficLight();
            }
    

2. Pequena caixa de bola colorida

  • Propriedades da classe Boll
    insira a descrição da imagem aqui

  • Métodos da classe Boll

    • método de inicialização init()
    • método de atualização update()
  • Realize a animação de várias bolinhas pequenas

    • Coloque cada instância de bola na mesma matriz
      • [{instância da bola},{instância da bola},{instância da bola},{instância da bola}]
    • Você só precisa usar um cronômetro para percorrer cada bola em cada quadro e chamar seu método de atualização
  • Exemplo de código:

     body{
          
          
            background-color: black;
          }
          .ball{
          
          
            position: absolute;
            border-radius: 50%;
          }
    
     //小球类
      function Ball(x,y){
          
          
        //属性x,y表示的是圆心的坐标
        this.x = x;
        this.y = y;
        //透明的
        this.opacity = 1;
        do{
          
          
            // 这个小球的x增量和y的增量
            this.dX = parseInt(Math.random()*20)-10;
            this.dY = parseInt(Math.random()*20)-10;
        }while(this.dX === 0 || this.dY === 0)
    
        // 小球的背景颜色
        this.color = colorArr[parseInt(Math.random()*colorArr.length)];
        // 小球半径
        this.r = 20;
        // 初始化
        this.init();
        // 把自己推入数组,注意:这里的this不是类本身,而是实例
          ballArr.push(this);
      }
      Ball.prototype.init = function (){
          
          
        //创建自己的dom
        this.dom = document.createElement('div');
        this.dom.className = 'ball';
        this.dom.style.width = this.r *2 +'px';
        this.dom.style.height = this.r *2 +'px';
        this.dom.style.left = this.x - this.r+'px';
        this.dom.style.top = this.y - this.r+'px';
        this.dom.style.backgroundColor = this.color;
        //上树
          document.body.appendChild(this.dom);
      }
    
      // 更新
      Ball.prototype.update = function (){
          
          
          // 位置改变
          this.x += this.dX;
          this.y -= this.dY;
          // 半径改变
          this.r += 0.2;
          // 透明度改变
          this.opacity -= 0.05;
          this.dom.style.width = this.r *2 +'px';
          this.dom.style.height = this.r *2 +'px';
          this.dom.style.left = this.x - this.r+'px';
          this.dom.style.top = this.y - this.r+'px';
          this.dom.style.opacity = this.opacity;
          // 当透明度小于0,就需要从数组中删除自己,DOM元素也要删除自己
          if(this.opacity<0){
          
          
              //从数组中删除自己
              for (var i = 0; i<ballArr.length;i++){
          
          
                  if(ballArr[i] == this){
          
          
                      ballArr.splice(i,1);
                  }
                  //还要删除自己的dom
                  document.body.removeChild(this.dom);
              }
          }
    
      };
    
    
      // 把所有的小球实例都放到一个数组中
      var ballArr = [];
      // 初始颜色数组
      var colorArr = ['#66CCCC','#CCFFCC','#FF99CC','#FF6666','#CC3399','#ff6600']
    
      // 定时器,负责更新所有的小球实例
      setInterval(function (){
          
          
          //遍历数组 ,调用update方法
          for(var i= 0;i<ballArr.length;i++){
          
          
            ballArr[i].update();
          }
      },20);
      // 鼠标指针的监听
      document.onmousemove = function (e){
          
          
          //得到鼠标指针的位置
          var x = e.clientX;
          var y = e.clientY;
          new Ball(x,y);
      }
    
    

Six, objetos internos do JS

1. Embalagem

  • Instâncias de Number(), String() e Boolean() são todos tipos de objeto e suas propriedades Primitivevalue armazenam seus próprios valores
  • O valor do tipo básico de new pode normalmente participar da operação
  • O objetivo das classes wrapper é permitir que valores básicos de tipo obtenham métodos do protótipo de seus construtores

2. Objeto matemático ★

  • Potência e raiz quadrada: Math.pow(), Math.sqrt()

  • Arredondamento para cima e para baixo: Math.ceil(), Math.floor()

  • Método Math.round(): arredondamento

    • arredondar para duas casas decimais
      insira a descrição da imagem aqui
  • Math.max(): Obtém o valor máximo da lista de parâmetros

    • Use Math.max() para encontrar o valor máximo de uma matriz
      • Math.max() exige que o parâmetro seja "listado", não um array

      • Com o método apply, ele pode especificar o contexto da função e passar "valores dispersos" como parâmetros da função na forma de um array

        var arr = [3,6,9,2];
        var max = Math.max.apply(null,arr);
        console.log(max);  // 9
        
  • Math.min(): Obtém o valor mínimo da lista de parâmetros

  • Math.random(): Obter um decimal entre 0 e 1

    • Para obter um número inteiro no intervalo [a,b], você pode usar esta fórmula:
      • parseInt(Math.random()*(ba +1))+a

3. Objeto de data ★

  • Use new Date() para obter o objeto de data da hora atual, que é um valor de tipo de objeto
    • Use new Date(2023,6,26) para obter o objeto de data da data especificada,
      • Observe que o segundo parâmetro representa o mês, começando em 0 e 6 representa julho
    • Também pode ser escrito como new Date('2023-07-26')
  • Métodos comuns do objeto de data
    insira a descrição da imagem aqui
  • carimbo de data/hora
    • O registro de data e hora representa o número de milissegundos a partir de um determinado momento em 1º de janeiro de 1970 às zero horas

    • O objeto de data pode ser transformado em um timestamp pelo método getTime() ou pela função Date.parse()

    • Ao escrever uma nova data (carimbo de data/hora), o carimbo de data/hora pode ser alterado para um objeto de data

7. Herança e construtores internos [Expandir]

  • construtor embutido

    • JavaScript tem muitos construtores embutidos . Por exemplo, Array é o construtor do tipo array, Function é o construtor do tipo de função e Object é o construtor do tipo de objeto.
    • O construtor embutido é muito útil. Todos os métodos deste tipo são definidos no protótipo de seu construtor embutido . Podemos adicionar novos métodos a este objeto para expandir a funcionalidade de um determinado tipo
    • Number\String\Boollearn é uma classe wrapper para três tipos básicos de valores. Chamá-los com new pode gerar versões de "objeto" de valores de tipo básico
  • Relações Construtoras Integradas

    • Qualquer função pode ser considerada como Função "nova", incluindo Objeto
      insira a descrição da imagem aqui
  • Com construtores (também conhecidos como "objetos falsos" ou "herança clássica")

    • Vantagens: Resolve o problema causado pelo valor do tipo de referência contido no protótipo e o problema deselegante do construtor da subclasse
    • Princípio: Chame o construtor da superclasse dentro do construtor da subclasse , mas preste atenção ao usar call() para vincular o contexto
        function People(name,sex,age){
          
          
          this.name = name;
          this.sex = sex;
          this.age = age;
          this.arr = [22,33,44];
        }
        function Student(name,sex,age,school,sid){
          
          
          People.call(this,name,sex,age);  // 借助构造函数
          this.school = school;
          this.sid = sid;
        }
        var xiaoming = new Student('小明','男',12,'CSDN学校',123455);
        console.log(xiaoming);
      </script>
    
  • Herança de composição (mais comumente usada)

    • Princípio: A tecnologia de pegar emprestado a cadeia de protótipos e pegar emprestado o construtor é combinada, também chamada de herança pseudoclássica
    • Desvantagens: Em qualquer caso, o construtor da superclasse será chamado duas vezes, uma ao criar o protótipo da subclasse e outra dentro do construtor da subclasse
        //父类
      function People(name,sex,age){
          
          
        this.name = name;
        this.sex = sex;
        this.age = age;
      }
      People.prototype.sayHello = function (){
          
          
          console.log('你好,我是'+this.name+'今年'+this.age+'岁了');
      }
      People.prototype.sleep = function (){
          
          
          console.log(this.name+'正在睡觉');
      }
    
        //子类
      function Student(name,sex,age,school,sid){
          
          
          //借助构造函数
          People.call(this,name,sex,age);
        this.school = school;
        this.sid = sid;
      }
      //实现继承:借助原型链
      Student.prototype = new People();
      Student.prototype.exam = function (){
          
          
          console.log(this.name + '正在考试')
      }
      Student.prototype.sayHello = function (){
          
          
          console.log('敬礼!你好,我是'+this.name+'今年'+this.age+'岁了'+this.school+'的学生');
      }
      var xiaoming = new Student('小明','男',12,'CSDN学校',123455);
      console.log(xiaoming);
      xiaoming.sayHello();
      xiaoming.sleep();
      xiaoming.exam();
    
  • herança prototípica

    • Conheça Object.creat()
      • O IE9+ passa a oferecer suporte ao método Object.create(), que pode criar um novo objeto com base no objeto especificado como um protótipo , sem recorrer ao construtor
      • Exemplo:var obj2 = Object.create(obj1);
        insira a descrição da imagem aqui
    • Conceito: No caso em que não há necessidade de criar um construtor, mas apenas se deseja que o novo objeto seja "semelhante" ao objeto existente, pode-se usar Object.create(), que é chamado de herança prototípica
  • herança parasitária

    • Escreva uma função que receba um parâmetro o, retorne um novo objeto p com o como protótipo e adicione um novo método pré-fabricado a p
      insira a descrição da imagem aqui
    • A herança parasitária é escrever uma função que pode " aprimorar o objeto ". Desde que o objeto seja passado para esta função, a função criará um novo objeto com base neste objeto e atribuirá um novo método predefinido ao novo objeto.
    • A herança parasitária também é um padrão útil nos casos em que os objetos são considerados principalmente em vez de tipos e construtores personalizados
    • Desvantagens: Usar herança parasitária para adicionar funções a objetos reduzirá a eficiência devido à incapacidade de obter a reutilização da função, ou seja, "o método não está escrito no protótipo "
  • herança composicional parasitária

    • Herdar propriedades emprestando construtores
    • Herança de métodos por meio de uma forma híbrida da cadeia de protótipos
    • Ideia básica: Não é necessário chamar o construtor do supertipo para especificar o protótipo do subtipo, basta uma cópia do protótipo do supertipo. Essencialmente, use a herança parasitária para herdar o protótipo do supertipo e, em seguida, atribuir o resultado ao protótipo do subtipo
  • instância do operador

    • O operador instanceof é usado para detectar "se um objeto é uma instância de uma determinada classe", como: xiaoming instanceof Student
    • Mecanismo subjacente: Verifique se o atributo Student.prototype está na cadeia de protótipos de xiaoming (quantas camadas estão bem, contanto que esteja lá)

Acho que você gosta

Origin blog.csdn.net/weixin_40845165/article/details/131920451
Recomendado
Clasificación