前端知识复习

1.symbol类型

Symbol 是 ECMAScript 6 中引入的一种新的基本数据类型,它表示独一无二的值。Symbol 值是通过 Symbol() 函数创建的。

Symbol 值具有以下特点:

  1. 独一无二性(唯一性):每个通过 Symbol() 函数创建的 Symbol 值都是唯一的,即使创建时传入相同的参数也不会相等。
  2. 不可改变性:Symbol 值一经创建就不可改变,不能添加属性或修改属性值。
  3. 作为属性名的唯一性:Symbol 值可以作为对象属性的键名(属性名),并且不会与其他属性名产生冲突。
  4. 隐藏性:Symbol 值不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 等方法返回,但是可以通过 Object.getOwnPropertySymbols() 方法获取。
// 创建一个 Symbol 值
const mySymbol = Symbol();

// 创建一个带描述的 Symbol 值
const mySymbolWithDescription = Symbol('This is a symbol');

// Symbol 值作为属性名
const obj = {
  [mySymbol]: 'value'
};

console.log(obj[mySymbol]); // 输出 'value'

// 获取所有 Symbol 属性
const symbolProperties = Object.getOwnPropertySymbols(obj);
console.log(symbolProperties); // 输出 [Symbol()]

// 使用全局 Symbol 注册表
const symbol1 = Symbol.for('symbolKey');
const symbol2 = Symbol.for('symbolKey');
console.log(symbol1 === symbol2); // 输出 true,因为它们在全局 Symbol 注册表中具有相同的键

 2.模版字面量

模板字面量使用反引号()来界定字符串,并可以在字符串中使用 ${}` 语法插入变量或表达式。以下是模板字面量的基本语法

const name = 'Alice';
const age = 30;

// 使用模板字面量创建字符串
const message = `Hello, my name is ${name} and I am ${age} years old.`;

console.log(message); // 输出 "Hello, my name is Alice and I am 30 years old."




const name = 'Alice';
const age = 30;

// 嵌套使用模板字面量
const message = `
  Hello, my name is ${name} and I am ${age} years old.

  Today is a ${new Date().toLocaleDateString()}.
`;

console.log(message);

3.defer和async的区别(DOMContentLoaded 是一个 DOM 事件,表示 HTML 文档已经完全加载并解析完成,但不包括外部资源(例如图片、样式表、嵌入的框架等)的加载。)

当浏览器加载 HTML 并遇到<script>...</script>标签时,它无法继续构建 DOM。它必须立即执行脚本。外部脚本<script src="..."></script>也是如此:浏览器必须等待脚本下载,执行下载的脚本,然后才能处理页面的其余部分。

这导致一个重要问题:

  • 如果页面顶部有一个庞大的脚本,它会“阻塞页面”。在下载并运行之前,用户无法看到页面内容

有一个解决方法,就是把脚本放到最底部。(但是对于长 HTML 文档,这可能会有明显的延迟。)

defer属性告诉浏览器不要等待脚本,浏览器会继续处理 HTML,构建 DOM。该脚本“在后台”加载,然后在 DOM 完全构建完成后再运行。

defer脚本总是在 DOM 准备好时执行(但在DOMContentLoaded事件之前)

defer脚本保持相对顺序来执行
async属性意味着该脚本是完全独立的:
浏览器不会阻止async脚本
其他脚本也不会等待async脚本,async脚本也不会等待其他脚本
DOMContentLoaded和async脚本不会互相等待

DOMContentLoaded可能在async脚本执行之前触发(如果async脚本在页面解析完成后完成加载)
或在async脚本执行之后触发(如果async脚本很快加载完成或在 HTTP 缓存中)
简单来说就是 async 脚本在后台加载完就立即运行

注意:async和defer属性都仅适用于外部脚本,如果script标签没有src属性,尽管写了async、defer属性也会被忽略

4.函数柯里化

函数柯里化(curry)是函数式编程里面的概念。curry的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

简单点来说就是:每次调用函数时,它只接受一部分参数,并返回一个函数,直到传递所有参数为止。

作用:参数复用提前返回和 延迟执行

function add(a) {
  return function(b) {
    return a + b;
  };
}

// 使用柯里化的函数
const add5 = add(5);
console.log(add5(3)); // 输出 8


延迟执行:柯里化可以将原本需要多个参数的函数转换为一系列接受单个参数的函数,这样就可以先传入部分参数,延迟执行函数的调用。
参数复用:柯里化可以将一部分参数固定下来,形成一个新的函数,这样就可以重复调用这个新函数并传入不同的参数,而不用重复传入固定的参数。
函数复用:柯里化可以帮助我们创建可以复用的函数,这样可以减少代码的重复编写。
。

5.call,apply,bind

call 和 apply 的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别。

bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。

function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Alice' };
greet.call(person); // 输出:Hello, Alice!
function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Bob' };
const args = ['Alice']; // 函数调用时的参数列表
greet.apply(person, args); // 输出:Hello, Alice!
function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Charlie' };
const boundGreet = greet.bind(person);
boundGreet(); // 输出:Hello, Charlie!

6.map()和parselnt()的区别

map() 方法会返回一个新数组,其中的元素是对原始数组中的每个元素应用回调函数的结果。

// 将数组中的每个元素乘以 2 
const numbers = [1, 2, 3, 4]; 
const doubled = numbers.map((num) => num * 2); 
console.log(doubled); // 输出: [2, 4, 6, 8]

parseInt() 函数会返回解析后的整数。如果无法解析,则返回 NaN(Not a Number)

parseInt('123', 5) // 将'123'看作 5 进制数,返回十进制数 38 => 1*5^2 + 2*5^1 + 3*5^0 = 38

7.事件冒泡和事件捕获

事件冒泡(从里到外)

<div id="outer">
    <p id="inner">Click me!</p> 
</div>


当我们点击P元素时,事件是这样传播的:

p
div
body
html
document 现代浏览器都支持事件冒泡,IE9、Firefox、Chrome和Safari则将事件一直冒泡到window对象。

事件捕获

当点击P元素时,事件的传播方向就变成了这样:

  1. document
  2. html
  3. body
  4. div
  5. p

事件代理:利用事件冒泡机制,将事件处理程序绑定到一个父级元素上,从而管理其子元素上的事件。

<ul id="parent">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>



// 获取父元素
const parent = document.getElementById('parent');

// 父元素上绑定事件处理程序
parent.addEventListener('click', function(event) {
    // 判断事件目标是否为子元素
    if (event.target.tagName === 'LI') {
        // 处理事件
        console.log('Clicked on item: ' + event.target.textContent);
    }
});

 8.var,let,const

  1. var: 函数作用域,会变量提升,可重复声明,可重新赋值。
  2. let: 块作用域,不会变量提升,不可重复声明,可重新赋值。
  3. const: 块作用域,不会变量提升,不可重复声明,不可重新赋值。

9. promise,async,await

一个Promise有三种可能的状态:

  1. pending(待定) :初始状态,既不是成功,也不是失败。
  2. fulfilled(已实现) :意味着操作成功完成。
  3. rejected(已拒绝) :意味着操作失败。

展示链式调用和错误处理

new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
})
.then(result => {
    console.log(result); // 输出 1
    return result * 2;
})
.then(result => {
    console.log(result); // 输出 2
    return result * 3;
})
.then(result => {
    console.log(result); // 输出 6
    return result * 4;
})
.catch(error => {
    console.log('捕获到错误:', error);
});

 asyncawait是建立在Promise之上的高级抽象,使得异步代码的编写和阅读更加接近于同步代码的风格。

通过在函数声明前加上async关键字,可以将任何函数转换为返回Promise的异步函数。这意味着你可以使用.then().catch()来处理它们的结果。

await关键字只能在async函数内部使用。它可以暂停async函数的执行,等待Promise的解决(resolve),然后以Promise的值继续执行函数。

async function asyncFunction() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("完成"), 1000)
  });

  let result = await promise; // 等待,直到promise解决 (resolve)
  console.log(result); // "完成"
}

asyncFunction();

手写promise代码

class MyPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onResolvedCallbacks.forEach(callback => callback());
            }
        };

        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(callback => callback());
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        const promise2 = new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            if (this.state === 'pending') {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });

        return promise2;
    }

    catch(onRejected) {
        return this.then(null, onRejected);
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    let called = false;

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            const then = x.then;
            if (typeof then === 'function') {
                then.call(x, value => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, value, resolve, reject);
                }, reason => {
                    if (called) return;
                    called = true;
                    reject(reason);
                });
            } else {
                resolve(x);
            }
        } catch (error) {
            if (called) return;
            called = true;
            reject(error);
        }
    } else {
        resolve(x);
    }
}

10.Ajax、Fetch、axios

 Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。

1)利用 XMLHttpRequest 模块实现 Ajax。

<body>
  <script>
    function ajax(url) {
      const xhr = new XMLHttpRequest();
      xhr.open("get", url, false);
      xhr.onreadystatechange = function () {
        // 异步回调函数
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            console.info("响应结果", xhr.response)
          }
        }
      }
      xhr.send(null);
    }
    ajax('https://smallpig.site/api/category/getCategory')
  </script>
</body>

注意: 我们使用这种方式实现网络请求时,如果请求内部又包含请求,以此循环,就会出现回调地狱,这也是一个诟病,后来才催生了更加优雅的请求方式。

2)Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。

Fetch 是一个 API,它是真实存在的,它是基于 promise 的。

<body>
  <script>
    function ajaxFetch(url) {
      fetch(url).then(res => res.json()).then(data => {
        console.info(data)
      })
    }
    ajaxFetch('https://smallpig.site/api/category/getCategory')
  </script>
</body>

 3)Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。

// 发送 POST 请求
axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
    }
})

Ajax、Fetch、axios三者之间的关系可以用一张图来清晰的表示,如图: 

 

11.原型对象与原型链

原型对象(prototype):每个函数都有一个特殊的属性叫做原型对象【prototype】

  • js是基于原型的语言,每个对象都拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。这些属性和方法时定义在对象的构造函数之上的prototype属性上,而非对象的实例本身。
  • 原型对象可以再拥有原型对象,并从中继承方法和属性,一层一层,层层向上直到一个对象的原型对象为 null,这种就是原型链。
  • 创建对象实例时,对象的实例和它的构造器之间建立一个链接【__proto__属性,是从构造函数的prototype属性派生的。也就是__proto__与构造函数的prototype是指向同个对象】Object.getPrototypeof(new Foobar())和Foobar.prototype是相等的
  • 查找属性的过程: 1.先查找自己身属性是否由包含该属性。 2.如果没有,才会沿着原型链,层层向上搜索,直到找到名字的属性 3.如果找到最后原型链的末尾,即最后的原型为null,那就是没有找到该属性。就会返回undefined

  • JS中的对象都内置了__proto__属性,但是只有函数对象内置了prototype属性,

  • 函数对象除了具有 __proto__ 属性外,还具有 prototype 属性。prototype 是函数对象独有的属性,它是在函数被创建时自动添加的,指向一个对象,该对象就是函数的原型。当函数作为构造函数被调用时,通过 new 关键字创建的实例对象会继承该函数的 prototype 对象上的属性和方法。

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log('Hello, my name is ' + this.name);
};


const person1 = new Person('Alice');


person1 对象会继承 Person.prototype 对象上的 sayHello 方法。

----------------------------------------------------分割线------------------------------------------------------

12. calc()

在编写 calc() 函数语法时,必须在每个操作之间包含空格,特别是在使用 + 和 - 操作符时,否则表达式将无效。

使用 calc() 实现动态的宽度或高度:
css
Copy code
.container {
    width: calc(100% - 20px);
}
将 calc() 与百分比和固定值一起使用:
css
Copy code
.sidebar {
    width: calc(25% - 10px);
}
在响应式布局中,结合 calc() 和视口单位:
css
Copy code
.header {
    height: calc(100vh - 50px);
}
将 calc() 用于调整字体大小:
css
Copy code
.text {
    font-size: calc(16px + 2vw);
}

13.动画与过渡

  • CSS Transition
    CSS 过渡,属于补间动画,即设置关键帧的初始状态,然后在另一个关键帧改变这个状态,比如大小、颜色、透明度等,浏览器将自动根据二者之间帧的值创建的动画。

  • .element {
        transition: property duration timing-function delay;
    }
    
  • CSS Animation
    CSS 动画,可以理解是 CSS Transition 的加强版,它既可以实现 补间动画 的动画效果,也可以使其以 逐帧动画 的方式进行绘制。

  • @keyframes animation-name {
        from { /* 初始状态 */ }
        to { /* 结束状态 */ }
    }
    
    .element {
        animation: animation-name duration timing-function delay iteration-count direction fill-mode play-state;
    }
    

14.实现等高布局 

HTML 代码:
html复制代码<div class="container">
  <div class="left">left</div>
  <div class="center">center</div>
  <div class="right">right</div>
</div>

CSS 代码:
css复制代码.left {
  background-color: red;
}
.center {
  background-color: green;
}
.right {
  background-color: blue;
}

1)padding + 负 margin

.container {
  overflow: hidden;
}
.center,
.left,
.right {
  padding-bottom: 10000px;
  margin-bottom: -10000px;
}

2)模仿 table 布局

.container {
  display: table;
}
.center,
.left,
.right {
  display: table-cell;
}

3)flex 实现

.container {
  display: flex;
}

flex 元素默认占满整个容器的高度,这样各栏高度均为容器高度,实现等高。
实际上使用的是 align-items: stretch 属性(默认)。

4)grid 实现

.container {
  display: grid;
  grid-auto-flow: column;
}

15.css实现三角形

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent; /* 左边框透明 */
  border-right: 50px solid transparent; /* 右边框透明 */
  border-top: 50px solid red; /* 上边框红色 */
}

16.display,opocity,visiblity

 display:none 与 visibility:hidden的区别

visibility 拥有继承性
visibility 不会影响 css 计数器
visibility 过渡效果有效,而 display 则无效
visibility 可以获得元素的尺寸位置,而 display 则无法获取
visibility 在无障碍访问这一块比 display 更友好

如果希望元素不可见,同时不占据空间,辅助设备无法访问,但资源有加载,DOM 可 访问,则可以直接使用 display:none 隐藏。

如果希望元素不可见,不能点击,辅助设备无法访问,但占据空间保留,则可以使用 visibility:hidden 隐藏。

如果希望元素不可见,但可以点击,而且不占据空间,则可以使用透明度opacity: 0;。

16. 多行文本的省略显示

1)使用-webkit-line-clamp

div {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  -webkit-line-clamp: 2;
}

除了-webkit-line-clamp其他属性固定不变,主要是将对象作为弹性伸缩盒子模型显示,并设置伸缩盒对象的子元素的排列方式。

而-webkit-line-clamp是用来控制多少行进行省略

2)绝对定位

.wrap {
    position: relative;
    /*line-height和height要相互配合,显示多少行就省略,就是line-height多少倍数*/
    line-height: 1.2em;
    max-height: 3.6em;
    /*此属性看需求来判断是否设置,因为设置了padding-right,多腾出了点位置,该值一般为padding-right的值的负值*/
    /*margin-left: -1em;*/
    /*此值写死成1em就好,因为省略号大概就是占用1em的空间*/
    padding-right: 1em;
    text-align: justify;
    overflow: hidden;
}

.wrap:before {
    position: absolute;
    right: 0;
    bottom: 0;
    content: '...';
}

.wrap:after {
    position: absolute;
    right: 0;
    /*宽高写死1em就好,因为省略号大概就是占用1em的空间,用来遮挡住省略号,也基本上跟wrap的padding-right一致*/
    width: 1em;
    /*与wrap的行高实际值保持一致*/
    height: 1.2em;
    content: '';
    /*要跟所在背景颜色一致才能遮挡住省略号后觉得没异样*/
    background-color: #fff;
}

3)float布局

.wrap {
    /*需要定高*/
    height: 100px;
    /*用来设置显示多少行才省略,值一般为wrap的height值/行数求得,但是这个行数会受到字体大小的限制*/
    /*字体太大了,设置显示很多行也会很丑,都挤一块了,所以这个实际值,要看具体需求和实践*/
    line-height: 25px;
    /*加上此属性显示效果更佳,就算部分浏览器不支持也影响不大*/
    text-align: justify;
    overflow: hidden;
}

.wrap:before {
    float: left;
    /*这个值可以随意设定,不论单位还是什么*/
    width: 1em;
    height: 100%;
    content: '';
}

.wrap:after {
    float: right;
    /*大小随意,设置em单位最好,可随字体大小变化而自适应*/
    /*如果要采用以下渐变效果,那么这个值要大于before里的width值效果会比较好点*/
	/*值越大,渐变的效果越明显影响的范围越大。*/
    width: 2.5em;
    /*与父元素wrap的行高实际px值一样*/
    height: 25px;
    /*此值要跟自身宽度一样,取负值*/
    margin-left: -2.5em;
    /*此值要跟before宽度一样*/
    padding-right: 1em;
    content: '...';
    text-align: right;
    /*这里开始利用在float布局的基础上进行定位移动了*/
    position: relative;
    /*与父元素wrap的行高实际值一样,取负值*/
    top: -25px;
    left: 100%;
    /*设置渐变效果是为了省略号和内容衔接得自然点,没那么突兀,要注意要跟文字所在的背景的颜色搭配(把white替换成背景色)*/
    background: #fff;
    background: -webkit-gradient(linear, left top, right top, from(rgba(255, 255, 255, 0)), to(white), color-stop(50%, white));
    background: -moz-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
    background: -o-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
    background: -ms-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
    background: linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
}

.wrap .text {
    float: right;
    /*该值要等于wrap:before的width值*/
    margin-left: -1em;
    width: 100%;
}

4)

.ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* 如果需要多行省略,请取消注释下面两行 */
  /* display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; */ /* 限制显示的行数 */
}

17.BFC

BFC容器"通常是指"块级格式化上下文容器"(Block Formatting Context Container)。块级格式化上下文(BFC)是在CSS中用来管理和控制元素在页面上布局和排列的一种机制。BFC容器是一种具有特定规则的HTML元素或CSS属性,它们会创建一个独立的上下文,影响其内部元素的布局和排列方式。BFC容器是CSS布局中的一个重要概念,可以帮助开发人员更精确地控制元素的布局和排列



BFC容器"通常是指"块级格式化上下文容器"(Block Formatting Context Container)。块级格式化上下文(BFC)是在CSS中用来管理和控制元素在页面上布局和排列的一种机制。BFC容器是一种具有特定规则的HTML元素或CSS属性,它们会创建一个独立的上下文,影响其内部元素的布局和排列方式。BFC容器是CSS布局中的一个重要概念,可以帮助开发人员更精确地控制元素的布局和排列



哪些属性可以创建BFC容器

float: left || right
position: absolute || fixed
display: inline-block;
display: table-cell ....
overflow: hidden || auto  || overly  || scroll
弹性盒子  (display: flex || inline-flex)

18.实现垂直居中

1)仅居中元素定宽高适用

  • absolute + 负margin
  • absolute + margin auto
  • absolute + calc

2)居中元素不定宽高

  • absolute + transform
  • lineheight
  • writing-mode
  • table
  • css-table
  • flex
  • grid
<div class="wp">
    <div class="box size">123123</div>
</div>

/* 公共代码 */
.wp {
    border: 1px solid red;
    width: 300px;
    height: 300px;
}

.box {
    background: green;    
}

.box.size{
    width: 100px;
    height: 100px;
}
/* 公共代码 */





 CSS实现水平垂直居中的10种方式 - 掘金

19.伪类和伪元素

1)伪类

伪类是添加到选择器的关键字,用于指定所选元素的特殊状态。例如,伪类 :hover 可以用于选择一个按钮,当用户的指针悬停在按钮上时,设置此按钮的样式。

前端常用的伪类有 :hover:focus:first-child:first-of-type:last-child:last-of-type:nth-child():not():has() 等等。

2)伪元素

伪元素是一个附加至选择器末的关键词,允许你对被选择元素的特定部分修改样式,例如 ::first-line 伪元素可以改变段落首行文字的样式。一个选择器只能使用一个相同类型的伪元素。

伪元素由冒号(::)后跟着伪元素名称组成,例如 ::before::after 等。

还在傻傻分不清常用伪类和伪元素?快来一文速览! - 掘金

20.超宽文本设置为省略号

.text-ellipsis {
  white-space: nowrap; /* 不换行 */
  overflow: hidden; /* 溢出隐藏 */
  text-overflow: ellipsis; /* 使用省略号 */
}

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值