由浅入深 65个JS常考面试题

本文深入剖析JavaScript面试中65道常见问题,涵盖数据类型、运算符、数据转换、类型判断、作用域、原型链、对象创建、继承方式、this、函数特性、事件处理、DOM&BOM、Ajax、延迟加载等多个方面,旨在帮助开发者全面理解和掌握JS核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由浅入深逐个击破 JS常考面试题(上篇)

1、 介绍一下JS的基本数据类型,值是如何存储的?

JavaScript一共有8种数据类型,其中有7种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6新增,表示独一无二的值)和BigInt(es10新增);
一种引用类型Object(本质上是由一组无序的名值对组成的),里面包含function、Array、Date,JS不支持任何自定义类型的机制,所有的值都是上述的八种数据类型之一。
如何存储???
基本数据类型:直接存储在栈中,占据空间小,大小固定,属于被频繁使用的数据,所以放入栈中存储
引用数据类型:同时存储在堆和栈中,占据空间大,大小不固定,引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址之后从堆中获得实体

2、&&、||、!!运算符能做什么

&&称为逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最后一个真值表达式。采用短路防止不必要的工作
||称为逻辑或,在其操作数中找到第一个真值表达式并返回它,采用短路防止不必要的工作
!!可以将右侧的值强制转换成布尔值,这也是将值转换成布尔值的一种简单方法

3、JS的数据类型的转换

  1. 转换为布尔值(调用Boolean()方法)
  2. 转换为数字(调用Number()、parseInt()、parseFloat())
  3. 转换为字符串(调用toString()或者String()方法)
    null和undefinede没有toString方法
    在这里插入图片描述

4、JS中数据类型的判断(typeof、instanceof、constructor、Object.prototype.toString.call())

**typeof:**对于原始类型来说,除了null都可以显示正确类型

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object     []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){
   });    // function
console.log(typeof {
   });              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object     null 的数据类型被 typeof 解释为 object

instanceof:
可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的prototype

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){
   } instanceof Function);       // true
console.log({
   } instanceof Object);                   // true    
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);

instanceof可以精准的判断引用数据类型(Array、Function、Object),而基本数据类型不能被instanceof精准判断。
MDN中的解释:instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。其意思就是判断对象是否是某一数据类型的实例,在这里字面量值2、true、‘str’不是实例,所以判断就是false
constructor:

console.log([].constructor == Array, {
   }.constructor == Object)
//true true

Object.prototype.toString.call()
用于精准的判断数据类型,借助于Object对象的原型方法toString,使用call来进行狸猫换太子

var getDataType=Object.prototype.toString;
console.log(getDataType.call('字符串'), getDataType.call(1), getDataType.call(true), getDataType.call(undefined),getDataType.call(null), getDataType.call({
   }), getDataType.call([]), getDataType.call(function(){
   }));
//[object String] [object Number] [object Boolean] [object Undefined] [object Null] [object Object] [object Array] [object Function]

5、undefined和undeclared的区别

已经在作用域中声明但是还没有赋值的变量是undefined,相反还没有在作用域中声明过的变量是undeclared

6. null 和 undefined 的区别?

undefined和null都是基本数据类型,这两个基本数据类型分别都只有一个值,就是undefined和null
undefined代表未定义,null代表的含义是空对象,一般变量声明了但是还没有定义返回undefined,null主要是用于赋值给一些可能会返回对象的变量作为初始化
当我们使用typeof判断数据类型的时候,null会返回Object,这是一个历史遗留问题,当我们使用双等号对两种类型的值进行比较的时候返回true,三等号的时候返回false

7、{}和[]的valueOf和toString的结果是什么?

toString会将对象转换成字符串,valueOf是把对象转换成基本数据类型的值
{}的valueOf结果是{},toString()的结果为"[Object Object]"
[]的valueOf结果是[],toString()的结果是""

8、JS的作用域和作用域链

作用域:
作用域就是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套作用域根据变量标识符进行变量查找
作用域链:
作用域链的作用就是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数
作用域链的本质上是一个指向变量对象的指针列表,变量对象是一个包含了执行环境中所有变量和函数的对象,作用域的前端始终都是当前执行上下文的变量对象,全局执行上下文的变量对象也就是全局对象始终是作用域链的最后一个对象,当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链一直向后找

9、JS创建对象的6种方式

(1)new操作符+Object创建对象

var person = new Object();
    person.name = "lisi";
    person.age = 21;
    person.family = ["lida","lier","wangwu"];
    person.say = function(){
   
        alert(this.name);
    }

(2)字面量创建对象

var person ={
   
        name: "lisi",
        age: 21,
        family: ["lida","lier","wangwu"],
        say: function(){
   
            alert(this.name);
        }
    };

由于上面两种创建方式使用同一接口创建了大量相似的对象,产生了大量的重复代码,因此产生了工厂模式
(3)工厂模式
工作原理就是用函数来封装创建对象的细节,从而调用函数来达到复用的目的,它只是简单的封装了复用代码,而没有建立起对象和类间的关系,没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是o对象,对象的类型都是Object,因此出现了构造函数模式)。

function createPerson(name,age,family) {
   
    var o = new Object();
    o.name = name;
    o.age = age;
    o.family = family;
    o.say = function(){
   
        alert(this.name);
    }
    return o;
}

var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   //instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object);                           //true

(4)构造函数模式
js中的每一个函数都可以作为构造函数,只要一个函数是通过new来调用的,那么我们就可以把它称为构造函数,执行构造函数***首先会创建一个对象,然后将对象的原型指向构造函数的prototype属性,然后执行上下文的this指向这个对象,最后在执行整个函数***,相对于工厂模式的优点就是所创建的对象和构造函数建立起了联系,因此我么可以通过原型来识别对象的类型,但是有一个缺点就是造成了不必要的函数对象创建,因为在js中函数也是一个对象因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存,因为函数实例是可以通用的

function Person(name,age,family) {
   
    this.name = name;
    this.age = age;
    this.family = family;
    this.say = function(){
   
        alert(this.name);
    }
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true
console.log(person1.constructor);      //constructor 属性返回对创建此对象的数组、函数的引用

(5)原型模式
因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

function Person() {
   
}

Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
   
    alert(this.name);
};
console.log(Person.prototype);   //Object{name: 'lisi', age: 21, family: Array[3]}

var person1 = new Person();        //创建一个实例person1
console.log(person1.name);        //lisi

var person2 = new Person();        //创建实例person2
person2.name = "wangwu";
person2.family = ["lida","lier","lisi"];
console.log(person2);            //Person {name: "wangwu", family: Array[3]}
// console.log(person2.prototype.name);         //报错
console.log(person2.age);              //21

(6)混合模式(构造函数+原型模式)

这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。

function Person(name,age,family){
   
    this.name = name;
    this.age = age;
    this.family = family;
}

Person.prototype = {
   
    constructor: Person,  //每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
    say: function(){
   
        alert(this.name);
    }
}

var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
console.log(person1);
var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
console.log(person2);

11、JS继承的方式

(1)以原型链的方式实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

function Parent(name,gender){
   
            this.name=name;
            this.gender=gender;
            this.list=[1,2,3]
        }
        Parent.prototype.eat=function(){
   
            console.log("晚餐时间到")
        }
        function Child(age){
   
            this.age=age;
        }
        Child.prototype=new Parent("李白","男");
        var child=new Child(20);
        var child2=new Child(30);
        child.eat();
        console.log(child.list,child2.list);// [1,2,3] [1,2,3]
        child.list.push(4)
        console.log(child.list);// [1,2,3,4]        
        console.log(child2.list);// [1,2,3,4]

(2)使用构造函数来继承,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

function Parent(name){
   
    this.name=name;
    }
    Parent.prototype.saiHi=function(){
   
        console.log("hello")
    }
    function Child(name,age,gender){
   
        Parent.call(this,name)
        this.age=age;
        this.gender=gender;
    }
    let child=new Child("王磊",20,"男")
    console.log(child.name);// 王磊
    child.sayHi(); // Uncaught TypeError:child.sayHi is not a function

这里使用的原理就是在Child里面,把Parent的this指向改为是Child的this指向,从而实现继承
(3)组合型
组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

function Person(school){
   
            this.school=school;
        }
        Person.prototype.skill=function(){
   
            console.log("学习");
        }
        function Student(school,name,age,gender){
   
            Parent.call(this,school);
            this.name=name;
            this.age=age;
            this.gender=gender;
        }
        Student.prototype=Person.prototype;
        let student=new Student("广铁一中","王菲菲",14,"女");
        console.log(Student.prototype===Person.prototype)
        console.log(student.constructor)

(4)
原型式继承(组合方式的优化)
原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

function Parent(name,play){
   
            this.name=name;
            this.play=play;
        }
        function Child(name,play,age){
   
            Parent.call(this,name,play);
            this.age=age;
        }
        Child.prototype=Object.create(Parent.prototype);// 隔离了父类和子类的构造函数,父类的添加到了__proto__属性上
        Child.prototype.constructor=Child
        let child=new Child("张三","玩",20);
        let child2=new Child("李四","吃",10)
       
        console.log(child.constructor)

(5)寄生式继承

12、谈谈你对this、call、apply、 bind的理解

  1. 在浏览器里面,在全局范围内this指向window对象
  2. 在函数中,this永远指向最后调用它的那个对象
  3. 构造函数中,this指向new出来的那个新对象
  4. call、apply、bind中的this被绑定在指定的那个对象上
  5. 箭头函数中的this比较特殊,箭头函数的this可以理解为父作用域的this,不是调用时的this,前面四种方式都是调用时才能确定,所以是动态的,而箭头函数的this指向的是静态的,声明的时候就确定下来
  6. apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参
    在这里插入图片描述

13、JS原型、原型链有什么特点?

原型:
在JS中我们使用构造函数来新建一个对象,每一个构造函数的内部都有一个prototype属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法,当我们使用构造函数创建一个对象后,在这个对象内部包含一个指针,这个指针指向构造函数prototype属性对应的值,在ES5中这个指针被称为对象的原型。一般来说我们是不应该能获取到这个值的,但是现在浏览器中都实现了proto属性来让我们访问这个属性,ES5中新增了Object.getProtoTypeOf()方法,我们可以通过这个方法来获取对象的原型。

原型链:
当我们访问一个对象的属性的时候,如果这个对象内部不存在这个属性,那么它就会去它原型对象的prototype里面找这个属性,这个原型对象又会有自己的原型,指向Object的原型对象,于是这样一层一层网上找,就是原型链的概念,原型链的尽头一般是Object.prototype所以这就是我们新建的对象为什么能够使用toString()方法的原因

特点:
JS对象是通过引用来传递的,我们创建的每个新对象实体中并没有自己的一份原型副本,当我们修改原型的时候,与之相关的对象也会继承这一改变

14、JS获取原型的方法?

p.proto
p.constructor.prototype
Object.getPrototypeOf§

15、什么是闭包?为什么要使用它?

闭包是指有权访问另一个函数作用域内的变量的函数,创建闭包的最常见的方式就是在一个函数内部创建另一个函数,创建的函数可以访问到当前函数的局部变量
用途:

  1. 闭包的第一个用途就是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量
  2. 闭包的另一个用途就是使已经运行结束的上下文中的变量继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收
function a(){
   
    var n = 0;
    function add(){
   
       n++;
       console.log(n);
    }
    return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1();    //1
a1();    //2  第二次调用n变量还在内存中

16、什么是DOM和BOM?

DOM指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了网页内容的方法和接口。

BOM指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互行为和接口,BOM的核心是window,而window对象具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象,这意味着在网页中定义的任何对象、变量和函数都作为全局对象的一个属性或者方法存在,window对象中含有location对象、navigator对象、screen对象等子对象,并且DOM的最根本的对象document对象也是BOM的window对象的子对象。

17、三种事件模型是什么?

事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现在浏览器一共有三种事件模型。

  1. DOM0级模型:这种模型不会传播,事件一旦发生将马上进行处理所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过js属性来指定监听函数,相同事件的监听函数只能绑定一个,这种方式是所有浏览器所兼容的。
  2. IE事件模型:在该事件模型中,一次事件一共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行,这种模型通过attachEvent来添加监听函数,可以添加多个监听函数会按顺序执行
  3. DOM2级事件模型:在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段,指的是事件从document一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段的和IE事件模型的两个阶段相同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。

18、什么是事件委托?

事件委托的本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这就是事件代理。

使用事件委托我们可以不必要为每个子元素绑定一个监听事件,这样减少了内存消耗,并且使用事件代理我们还可以实现事件的动态绑定,比如新增一个子节点,我们并不需要单独的为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。

19、什么是事件传播?

当事件发生在DOM元素上时,该事件并不完全发生在那个元素上,在“当事件发生在DOM元素上时,该事件并不完全发生在那个元素上”,在冒泡阶段中,事件冒泡或向上传播至父级,祖父母,直到到达window为止,而在捕获阶段,事件从window开始向下触发元素事件或者event.target
事件传播分成三个阶段:
1、捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素。
2、目标阶段–事件已达到目标元素。
3、冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。

20、什么是事件捕获?

当事件发生在DOM元素上的时候,该事件并不完全发生在那个元素上,在捕获阶段,事件从window开始,一直到触发事件的元素,window----->document—>html—>body—>目标元素
addEventListener方法具有第三个可选参数usecapture,其默认值是false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生,如果单击child元素,它将分别在控制台上打印window,document,html,grandparent和parent,这就是事件捕获。
在这里插入图片描述

21、什么是事件冒泡?

事件冒泡刚好与事件捕获相反,当前元素—>body—>html—>document---->window,当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。
addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。如果单击child元素,它将分别在控制台上打印child,parent,grandparent,html,document和window,这就是事件冒泡。

22、DOM操作–怎样添加、移除、移动、复制、创建和查找节点?

  1. 创建新节点

createDocumentFragment()创建一个DOM片段
createElement()创建一个具体的元素
createTextNode()创建一个文本节点

  1. 添加、移除、替换、插入

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

  1. 查找

getElementById()
getElementByname()
getElementByClassName()
querySelector()
querySelectorAll()

  1. 属性操作

getAttribute(key)
setAttribute(key,value)
hasAttribute(key)
removeAttribute(key)

23、js数组和对象有哪些原生的方法

在这里插入图片描述
在这里插入图片描述

24、常用正则表达式

//(1)匹配 16 进制颜色值
var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

//(2)匹配日期,如 yyyy-mm-dd 格式
var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

//(3)匹配 qq 号
var qq = /^[1-9][0-9]{4,10}$/g;

//(4)手机号码正则
var phone = /^1[34578]\d{9}$/g;

//(5)用户名正则
var username = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

//(6)Email正则
var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

//(7)身份证号(18位)正则
var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;

//(8)URL正则
var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;

// (9)ipv4地址正则
var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

// (10)//车牌号正则
var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;

// (11)强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):var pwd = /^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$/

25、Ajax是什么?如何创建一个Ajax?

ajax它是一种异步通信的方法,通过直接由JS脚本向服务器发起http通信,然后根据服务器返回数据,更新网页的相应部分,而不用刷新整个页面。
创建步骤:

  • 创建XMLHttpRequest对象
  • 配置Ajax请求地址
  • 发送请求
  • 监听请求,接受响应
    面试要求手写(原生):
//1、创建Ajax对象
var xhr=window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');//兼容IE6及以下的版本
//2、配置Ajax请求地址
xhr.open('get','index.html',true);
//3、发送请求
xhr.send(null);//严谨写法
//4、监听请求,接受响应
xhr.onreadystatechange=fucntion(){
   
//xhr.readyState==4表示响应内容解析完成可以在客户端调用
	if(xhr.readyState==4&&xhr.status==200||xhr.status==304)
	console.log(xhr.responseXML)
}

jquery写法:

$.ajax({
   
	type:'post',
	url:'',
	async:true,//async是异步,sync是同步
	data:data,//针对post请求
	dataType:'jsonp',
	success:function(msg)
	{
   
	},
	error:function(error)
	{
   
	}
})

promise封装实现:

function getJSON(url)
{
   	
		//创建一个promise对象
		let promise=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值