JS面试题汇总(三)

往期点这里:↓
JS面试题汇总(一)
JS面试题汇总(二)
21. for in、Object. keys 和 Object. getOwnPropertyNames 对属性遍历有什么区别?

参考答案:

  • for in 会遍历自身及原型链上的可枚举属性
  • Object. keys 会将对象自身的可枚举属性的 key 输出
  • Object. getOwnPropertyNames会将自身所有的属性的 key 输出

解析:

ECMAScript 将对象的属性分为两种:数据属性和访问器属性。

var parent = Object.create(Object.prototype, {
    a: {
        value: 123,
        writable: true,
        enumerable: true,
        configurable: true
    }
});
// parent继承自Object.prototype,有一个可枚举的属性a(enumerable:true)。

var child = Object.create(parent, {
    b: {
        value: 2,
        writable: true,
        enumerable: true,
        configurable: true
    },
    c: {
        value: 3,
        writable: true,
        enumerable: false,
        configurable: true
    }
});
//child 继承自 parent ,b可枚举,c不可枚举

for in

for (var key in child) {
    console.log(key);
}
// b
// a
// for in 会遍历自身及原型链上的可枚举属性

如果只想输出自身的可枚举属性,可使用 hasOwnProperty 进行判断(数组与对象都可以,此处用数组做例子)

let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
    if (arr.hasOwnProperty(i)) {
        console.log(arr[i]);
    }
}
// 1
// 2
// 3

Object. keys

console.log(Object.keys(child));
// ["b"]
// Object.keys 会将对象自身的可枚举属性的key输出

Object. getOwnPropertyNames

console.log(Object.getOwnPropertyNames(child));
// ["b","c"]
// 会将自身所有的属性的key输出

22. iframe 跨域通信和不跨域通信

参考答案:

不跨域通信

主页面

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title></title>
</head>

<body>
    <iframe name="myIframe" id="iframe" class="" src="flexible.html" width="500px" height="500px">
    </iframe>
</body>
<script type="text/javascript" charset="utf-8">
    function fullscreen() {
        alert(1111);
    }
</script>

</html>

子页面 flexible. html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title></title>
</head>

<body>
    我是子页面
</body>
<script type="text/javascript" charset="utf-8">
    // window.parent.fullScreens()
    function showalert() {
        alert(222);
    }
</script>

</html>

1、主页面要是想要调取子页面的 showalert 方法

myIframe.window.showalert();

2、子页面要掉主页面的 fullscreen 方法

window.parent.fullScreens();

3、js 在 iframe 子页面获取父页面元素:

window.parent.document.getElementById("元素id");

4、js 在父页面获取 iframe 子页面元素代码如下:

window.frames["iframe_ID"].document.getElementById("元素id");

跨域通信

使用postMessage(官方用法)

子页面

window.parent.postMessage("hello", "http://127.0.0.1:8089");

父页面接收

window.addEventListener("message", function(event) {
    alert(123);
});

解析:参考

23. H5 与 Native 如何交互

参考答案:

jsBridge

解析:参考

24. 如何判断一个对象是否为数组

参考答案:

第一种方法:使用 instanceof 操作符。

第二种方法:使用 ECMAScript 5 新增的 Array. isArray()方法。

第三种方法:使用使用 Object. prototype 上的原生 toString()方法判断。

25. <script> 标签的 defer 和 asnyc 属性的作用以及二者的区别?

参考答案:

  • 1、defer 和 async 的网络加载过程是一致的,都是异步执行。
  • 2、区别在于加载完成之后什么时候执行,可以看出 defer 是文档所有元素解析完成之后才执行的。
  • 3、如果存在多个 defer 脚本,那么它们是按照顺序执行脚本的,而 async,无论声明顺序如何,只要加载完成就立刻执行

解析:

无论 <script> 标签是嵌入代码还是引用外部文件,只要不包含 defer 属性和 async 属性(这两个属性只对外部文件有效),浏览器会按照 <script> 的出现顺序对他们依次进行解析,也就是说,只有在第一个 <script> 中的代码执行完成之后,浏览器才会执行第二个 <script> 中的代码,并且在解析时,页面的处理会暂时停止。

嵌入代码的解析=执行
外部文件的解析=下载+执行

script 标签存在两个属性,defer 和 async,这两个属性只对外部文件有效

只有一个脚本的情况

<script src = "a.js" />

没有 defer 或 async 属性,浏览器会立即下载并执行相应的脚本,并且在下载和执行时页面的处理会停止。

<script defer src = "a.js" />

有了 defer 属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,等到文档解析完成脚本才会执行。

<script async src = "a.js" />

有了 async 属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,下载完成后立即执行,执行过程中页面处理会停止。

<script defer async src = "a.js" />

如果同时指定了两个属性, 则会遵从 async 属性而忽略 defer 属性。

下图可以直观的看出三者之间的区别:

在这里插入图片描述

其中蓝色代表 js 脚本网络下载时间,红色代表 js 脚本执行,绿色代表 html 解析。

多个脚本的情况

这里只列举两个脚本的情况:

<script src = "a.js"> </script>
<script src = "b.js"> </script>

没有 defer 或 async 属性,浏览器会立即下载并执行脚本 a. js,在 a.js 脚本执行完成后才会下载并执行脚本 b. js,在脚本下载和执行时页面的处理会停止。

<script defer src = "a.js"> </script>
<script defer src = "b.js"> </script>

有了 defer 属性,浏览器会立即下载相应的脚本 a.js 和 b. js,在下载的过程中页面的处理不会停止,等到文档解析完成才会执行这两个脚本。HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于 DOMContentLoaded 事件执行。
在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本。

<script async src = "a.js"> </script>
<script async src = "b.js"> </script>

有了 async 属性,浏览器会立即下载相应的脚本 a.js 和 b. js,在下载的过程中页面的处理不会停止,a.js 和 b.js 哪个先下载完成哪个就立即执行,执行过程中页面处理会停止,但是其他脚本的下载不会停止。标记为 async 的脚本并不保证按照制定它们的先后顺序执行。异步脚本一定会在页面的 load 事件前执行,但可能会在 DOMContentLoaded 事件触发之前或之后执行。

参考

26. Object. prototype. toString. call() 和 instanceOf 和 Array. isArray() 区别好坏

参考答案:

  • Object. prototype. toString. call()
    • 优点:这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
    • 缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]
  • instanceOf
    • 优点:instanceof 可以弥补 Object. prototype. toString. call()不能判断自定义实例化对象的缺点。
    • 缺点: instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。
  • Array. isArray()
    • 优点:当检测 Array 实例时,Array. isArray 优于 instanceof ,因为 Array. isArray 可以检测出 iframes
    • 缺点:只能判别数组

解析:

Object. prototype. toString. call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = ["Hello", "An"];
an.toString(); // "Hello,An"
Object.prototype.toString.call(an); // "[object Array]"

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

Object.prototype.toString.call("An"); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function() {}); // "[object Function]"
Object.prototype.toString.call({
    name: "An"
}); // "[object Object]"

缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]

function f(name) {
    this.name = name;
}
var f1 = new f("martin");
console.log(Object.prototype.toString.call(f1)); //[object Object]

Object.prototype.toString.call(); // 常用于判断浏览器内置对象。

instanceof

instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。

使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。

[] instanceof Array; // true

但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

[] instanceof Object; // true

优点:instanceof 可以弥补 Object. prototype. toString. call()不能判断自定义实例化对象的缺点。

缺点:instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。

function f(name) {
    this.name = name;
}
var f1 = new f("martin");
console.log(f1 instanceof f); //true

Array. isArray()

  • 功能:用来判断对象是否为数组

  • instanceof 与 isArray

当检测 Array 实例时,Array. isArray 优于 instanceof ,因为 Array. isArray 可以检测出 iframes

var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length - 1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]

// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false

缺点:只能判别数组

  • Array. isArray() 与 Object. prototype. toString. call()

Array. isArray()是 ES5 新增的方法,当不存在 Array. isArray() ,可以用 Object. prototype. toString. call() 实现。

if (!Array.isArray) {
    Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === "[object Array]";
    };
}

参考

27. 什么是面向对象?

参考答案:

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

解析:

  • 面向对象和面向过程的异同
    • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
    • 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

28. 你对松散类型的理解

参考答案:

JavaScript 中的变量为松散类型,所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值,就是不像 SQL 一样申明某个键值为 int 就只能保存整型数值,申明 varchar 只能保存字符串。一个变量所保存值的类型也可以改变,这在 JavaScript 中是完全有效的,只是不推荐。相比较于将变量理解为“盒子“,《JavaScript 编程精解》中提到应该将变量理解为“触手”,它不保存值,而是抓取值。这一点在当变量保存引用类型值时更加明显。

JavaScript 中变量可能包含两种不同的数据类型的值:基本类型和引用类型。基本类型是指简单的数据段,而引用类型指那些可能包含多个值的对象。

29. JS 严格模式和正常模式

参考答案:

严格模式使用"use strict";

作用:

  • 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的 Javascript 做好铺垫。

表现:

  • 严格模式下, delete 运算符后跟随非法标识符(即 delete 不存在的标识符),会抛出语法错误; 非严格模式下,会静默失败并返回 false
  • 严格模式中,对象直接量中定义同名属性会抛出语法错误; 非严格模式不会报错
  • 严格模式中,函数形参存在同名的,抛出错误; 非严格模式不会
  • 严格模式不允许八进制整数直接量(如:023)
  • 严格模式中,arguments 对象是传入函数内实参列表的静态副本;非严格模式下,arguments 对象里的元素和对应的实参是指向同一个值的引用
  • 严格模式中 eval 和 arguments 当做关键字,它们不能被赋值和用作变量声明
  • 严格模式会限制对调用栈的检测能力,访问 arguments. callee. caller 会抛出异常
  • 严格模式 变量必须先声明,直接给变量赋值,不会隐式创建全局变量,不能用 with,
  • 严格模式中 call apply 传入 null undefined 保持原样不被转换为 window

解析:

一、概述

除了正常运行模式,ECMAscript 5 添加了第二种运行模式:“严格模式”(strict mode)。顾名思义,这种模式使得 Javascript 在更严格的条件下运行。

设立"严格模式"的目的,主要有以下几个:

  • 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;

  • 消除代码运行的一些不安全之处,保证代码运行的安全;

  • 提高编译器效率,增加运行速度;

  • 为未来新版本的 Javascript 做好铺垫。

"严格模式"体现了 Javascript 更合理、更安全、更严谨的发展方向,包括 IE 10 在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。

另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解 Javascript,让你变成一个更好的程序员。

本文将对"严格模式"做详细介绍。

二、进入标志

进入"严格模式"的标志,是下面这行语句:

“use strict”;

老版本的浏览器会把它当作一行普通字符串,加以忽略。

三、如何调用

"严格模式"有两种调用方法,适用于不同的场合。

3. 1 针对整个脚本文件

将"use strict"放在脚本文件的第一行,则整个脚本都将以"严格模式"运行。如果这行语句不在第一行,则无效,整个脚本以"正常模式"运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

(严格地说,只要前面不是产生实际运行结果的语句,"use strict"可以不在第一行,比如直接跟在一个空的分号后面。)

  <script>
    "use strict";
    console.log("这是严格模式。");
  </script>

  <script>
    console.log("这是正常模式。");kly, it's almost 2 years ago now. I can admit it now - I run it on my school's network that has about 50 computers.
  </script>

上面的代码表示,一个网页中依次有两段 Javascript 代码。前一个 script 标签是严格模式,后一个不是。

3. 2 针对单个函数

将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。

function strict() {
    "use strict";
    return "这是严格模式。";
}

function notStrict() {
    return "这是正常模式。";
}

3. 3 脚本文件的变通写法

因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

(function() {
    "use strict"; // some code here

})();

四、语法和行为改变

严格模式对 Javascript 的语法和行为,都做了一些改变。

4. 1 全局变量显式声明

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。

"use strict";

v = 1; // 报错,v未声明

for (i = 0; i < 2; i++) {
    // 报错,i未声明
}

因此,严格模式下,变量都必须先用 var 命令声明,然后再使用。

4. 2 静态绑定

Javascript 语言的一个特点,就是允许"动态绑定",即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。

严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。

具体来说,涉及以下几个方面。

(1)禁止使用 with 语句

因为 with 语句无法在编译时就确定,属性到底归属哪个对象。

"use strict";

var v = 1;

with(o) { // 语法错误
    v = 2;
}

(2)创设 eval 作用域

正常模式下,Javascript 语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval 作用域。

正常模式下,eval 语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval 语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于 eval 内部。

"use strict";

var x = 2;

console.info(eval("var x = 5; x")); // 5

console.info(x); // 2

4. 3 增强的安全措施

(1)禁止 this 关键字指向全局对象

function f() {
    return !this;
} // 返回false,因为"this"指向全局对象,"!this"就是false
function f() {
    "use strict";
    return !this;
} // 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。

因此,使用构造函数时,如果忘了加 new,this 不再指向全局对象,而是报错。

function f() {
    "use strict";

    this.a = 1;
}

f(); // 报错,this未定义

(2)禁止在函数内部遍历调用栈

function f1() {
    "use strict";

    f1.caller; // 报错

    f1.arguments; // 报错
}

f1();

4. 4 禁止删除变量

严格模式下无法删除变量。只有 configurable 设置为 true 的对象属性,才能被删除。

"use strict";

var x;

delete x; // 语法错误

var o = Object.create(null, {
    'x': {
        value: 1,
        configurable: true
    }
});

delete o.x; // 删除成功

4. 5 显式报错

正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。

"use strict";

var o = {};

Object.defineProperty(o, "v", {
    value: 1,
    writable: false
});

o.v = 2; // 报错

严格模式下,对一个使用 getter 方法读取的属性进行赋值,会报错。

"use strict";

var o = {
    get v() {
        return 1;
    }
};

o.v = 2; // 报错

严格模式下,对禁止扩展的对象添加新属性,会报错。

"use strict";

var o = {};

Object.preventExtensions(o);

o.v = 1; // 报错

严格模式下,删除一个不可删除的属性,会报错。

"use strict";

delete Object.prototype; // 报错

4. 6 重名错误

严格模式新增了一些语法错误。

(1)对象不能有重名的属性

正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。

"use strict";

var o = {
    p: 1,
    p: 2
}; // 语法错误

(2)函数不能有重名的参数

正常模式下,如果函数有多个重名的参数,可以用 arguments[i]读取。严格模式下,这属于语法错误。

"use strict";

function f(a, a, b) { // 语法错误

    return;

}

4. 7 禁止八进制表示法

正常模式下,整数的第一位如果是 0,表示这是八进制数,比如 0100 等于十进制的 64。严格模式禁止这种表示法,整数第一位为 0,将报错。

"use strict";

var n = 0100; // 语法错误

4. 8 arguments 对象的限制

arguments 是函数的参数对象,严格模式对它的使用做了限制。

(1)不允许对 arguments 赋值

"use strict";

arguments++; // 语法错误

var obj = {
    set p(arguments) {}
}; // 语法错误

try {} catch (arguments) {} // 语法错误

function arguments() {} // 语法错误

var f = new Function("arguments", "'use strict'; return 17;"); // 语法错误

(2)arguments 不再追踪参数的变化

function f(a) {
    a = 2;

    return [a, arguments[0]];
}

f(1); // 正常模式为[2,2]

function f(a) {
    "use strict";

    a = 2;

    return [a, arguments[0]];
}

f(1); // 严格模式为[2,1]

(3)禁止使用 arguments. callee

这意味着,你无法在匿名函数内部调用自身了。

"use strict";

var f = function() {
    return arguments.callee;
};

f(); // 报错

4. 9 函数必须声明在顶层

将来 Javascript 的新版本会引入"块级作用域"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

"use strict";

if (true) {
    function f() {} // 语法错误
}

for (var i = 0; i < 5; i++) {
    function f2() {} // 语法错误
}

4. 10 保留字

为了向将来 Javascript 的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。

使用这些词作为变量名将会报错。

function package(protected) { // 语法错误

    "use strict";

    var implements; // 语法错误

}

此外,ECMAscript 第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的 const 保留字,也是不能作为变量名的。

参考

30. 移动端 click 事件、touch 事件、tap 事件的区别

参考答案:

1. click 事件在移动端会有 200-300ms 的延迟,主要原因是苹果手机在设计时,考虑到用户在浏览网页时需要放大,所以,在用户点击的 200-300ms 之后,才触发 click,如果 200-300ms 之内还有 click,就会进行放大缩小。

2. touch 事件是针对触屏手机上的触摸事件。现今大多数触屏手机 webkit 内核提供了 touch 事件的监听,让开发者可以获取用户触摸屏幕时的一些信息。其中包括:touchstart, touchmove, touchend, touchcancel 这四个事件,touchstart touchmove touchend 事件可以类比于 mousedown mouseover mouseup 的触发

3. tap 事件在移动端,代替 click 作为点击事件,tap 事件被很多框架(如 zepto)封装,来减少这延迟问题, tap 事件不是原生的,所以是封装的,那么具体是如何实现的呢?

  < script >
      function tap(ele, callback) {
          // 记录开始时间
          var startTime = 0,
              // 控制允许延迟的时间
              delayTime = 200,
              // 记录是否移动,如果移动,则不触发tap事件
              isMove = false;

          // 在touchstart时记录开始的时间
          ele.addEventListener('touchstart', function(e) {
              startTime = Date.now();
          });

          // 如果touchmove事件被触发,则isMove为true
          ele.addEventListener('touchmove', function(e) {
              isMove = true;
          });

          // 如果touchmove事件触发或者中间时间超过了延迟时间,则返回,否则,调用回调函数。
          ele.addEventListener('touchend', function(e) {
              if (isMove || (Date.now() - startTime > delayTime)) {
                  return;
              } else {
                  callback(e);
              }
          })
      }

  var btn = document.getElementById('btn');
  tap(btn, function() {
      alert('taped');
  }); <
  /script>

拓展:

点透问题

如果我们在移动端所有的 click 都替换为了 tap 事件,还是会触发点透问题的,因为实质是: 在同一个 z 轴上,z-index 不同的两个元素,上面的元素是一个绑定了 tap 事件的,下面是一个 a 标签,一旦 tap 触发,这个元素就会 display: none,而从上面的 tap 可以看出,有 touchstart、touchend,所以会 300ms 之后触发 click 事件,而 z-index 已经消失了,所以,触发了下面的 a 的 click 事件,注意: 我们认为 a 标签默认是绑定了 click 事件的。而这种现象不是我们所期待的。

解决方案: (1)使用 fastclick。 (2)添加一个延迟。

(1)直接引入 fastclick 库。

window.addEventListener(
    "load",
    function() {
        FastClick.attach(document.body);
    },
    false
);

这样,就可以成功解决问题了。

(2)对于上一个 tap 做延迟。

tap(ele, function() {
    setTimeout(function() {
        ele.style.display = "none";
    }, 300);
});

这样,过了 300ms,那么 click 事件就不会触发在下面的 a 标签上了。

  • 1
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:点我我会动 设计师:我叫白小胖 返回首页
评论

打赏作者

柠檬树上柠檬果柠檬树下你和我

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值