JavaScript手写题

编程入门 行业动态 更新时间:2024-10-23 18:32:10

<a href=https://www.elefans.com/category/jswz/34/1771426.html style=JavaScript手写题"/>

JavaScript手写题

手动实现map方法(面试:用友、猿辅导、字节)

  • 回调函数接受三个参数。分别为:数组元素,元素索引,原数组本身
  • map方法执行的时候,会自动跳过未被赋值或者被删除的索引
  • map方法返回一个新数组,而且不会改变原数组。当然,你想改变也是可以的,通过回调函数的第三个参数,即可改变原数组。
// thisArg参数就是用来改变回调函数内部this的
Array.prototype.myMap = function (fn, thisArg) {// // 首先,检查传递的参数是否正确。if (typeof fn !== "function") {throw new TypeError(fn + " is not a function");}// 每次调用此函数时,我们都会创建一个 res 数组, 因为我们不想改变原始数组。let res = [];for (let i = 0; i < this.length; i++) {// 简单处理空项this[i] ? res.push(fn.call(thisArg, this[i], i, this)) : res.push(this[i]);}return res;
};//测试
const obj = {name: 'ha',age: 12
}const arr = [1, 3, , 4];
// 原生map
const newArr = arr.map(function (ele, index, arr) {console.log(this);return ele + 2;}, obj);console.log(newArr);// 用reduce实现map方法(字节)
// 方法1:
Array.prototype.myMap = function (fn, thisArg) {if (this === null) {throw new TypeError("this is null or not defined");}if (typeof fn !== "function") {throw new TypeError(fn + " is not a function");}return this.reduce((acc, cur, index, array) => {const res = fn.call(thisArg, cur, index, array);acc.push(res);return acc;}, []);
};// 方法2:
Array.prototype.myMap = function(fn, thisArg){if (this === null) {throw new TypeError("this is null or not defined");}if (typeof fn !== "function") {throw new TypeError(fn + " is not a function");}var res = [];this.reduce(function(pre, cur, index, arr){return res.push(fn.call(thisArg, cur, index, arr));}, []);return res;
}var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){console.log(item,index,arr);
})

参考链接
参考链接

实现reduce方法

Array.prototype.myReduce = function(fn, initValue) {// 边界条件判断if(typeof fn !== 'function') {console.error('this is not a function');}// 初始值let preValue, curValue, curIndex;if(typeof initValue === 'undefined') {preValue = this[0];curValue = this[1];curIndex = 1;} else {preValue = initValue;curValue = this[0];curIndex = 0;  }// 遍历for (let i = 0; i < this.length; i++) {preValue = fn(preValue, this[i], i, this)}return preValue;
}

reduce手写方法链接

实现promise.all(美团一面)

function promiseAll(promises) {return new Promise((resolve, reject) => {// 边界条件判断if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`);}// 成功的数量var resolvedCounter = 0;// 保存的结果var resolvedResult = [];for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then(value => {resolvedCounter++;resolvedResult[i] = value;// 当所有的promise都成功之后if (resolvedCounter == promises.length) {resolve(resolvedResult)}}, error=>{reject(error)})}})
}

Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
错误处理:
有时候我们使用Promise.all()执行很多个网络请求,可能有一个请求出错,但我们并不希望其他的网络请求也返回reject,要错都错,这样显然是不合理的。如何做才能做到promise.all中即使一个promise程序reject,promise.all依然能把其他数据正确返回呢?
方法1:当promise捕获到error 的时候,代码吃掉这个异常,返回resolve,约定特殊格式表示这个调用成功了

实现promise.race(58同城一面)

Promise.race = function (promises) {return new Promise((resolve, reject) => {promises.forEach(promise => {promise.then(resolve, reject)})})
}// 改进方法
Promise._race = promises => new Promise((resolve, reject) => {promises.forEach(promise => {promise.then(resolve, reject)})
})

模拟实现一个 Promise.finally

Promise.prototype.finally = function (callback) {let P = this.constructor;return this.then(value  => P.resolve(callback()).then(() => value),reason => P.resolve(callback()).then(() => { throw reason }));
};

链接

防抖(面试)

// 防抖
//参数func:需要防抖的函数
//参数delayTime:延时时长,单位ms
function debounce(func, delayTime) {//用闭包路缓存延时器idlet timer;return function (...args) {if (timer) {clearTimeout(timer);  //清除-替换,把前浪拍死在沙滩上} timer = setTimeout(() => { // 延迟函数就是要晚于上面匿名函数func.apply(this, args); // 执行函数}, delayTime);}
}// 测试
const task = () => { console.log('run task') }
const debounceTask = debounce(task, 1000)
window.addEventListener('scroll', debounceTask)

节流(面试)

// 节流函数
function throttle(fn, delay) {let timer = null;return function(...args) {if(timer) {return;}timer = setTimeout(() => {fn.apply(this, args);timer = null;}, delay)}
}// 测试
function print(e) {console.log('123', this, e)
}
input.addEventListener('input', throttle(print, 1000));

new(面试中问到)

function mynew(Func, ...args) {// 1.创建一个新对象const obj = {};// 2.新对象原型指向构造函数原型对象obj.__proto__ = Func.prototype;// 3.将构建函数的this指向新对象 (让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性))let result = Func.apply(obj, args);// 4.根据返回值判断return result instanceof Object ? result : obj;
}// 测试
function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.say = function () {console.log(this.name)
}let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui

注意:

类型说明
不 return 和 return 值类型结果就是输出 Person { name: ‘wang’ },这是正常的。
return 引用类型输出了 { age: 18 },也就是我们return的引用类型,此时,我们若创建原型方法也不会挂到实例上,调用时会报错TypeError
function Person(name) {this.name = name;// return 1; // 情况1:return 值类型,结果为{name: 'wang'}// return { age: 18 }; // 情况2:return 引用类型,结果为{ age: 18 }
}
const p = new Person('wang')
console.log(p)

事件总线 | 发布订阅模式(快手、滴滴)

✅参考链接
手写js发布订阅模式以及观察者模式

// 发布订阅模式
class EventEmitter {constructor() {// 事件对象,存放订阅的名字和事件this.events = {};}// 订阅事件的方法on(eventName, callback) {// 判断事件名是否是 string 类型if (typeof eventName !== "string") {throw TypeError("传入的事件名数据类型需为string类型")}// 判断事件函数是否是 function 类型if (typeof eventCallback !== "function") {throw TypeError("传入的回调函数数据类型需为function类型")}if (!this.events[eventName]) {// 注意时数据,一个名字可以订阅多个事件函数this.events[eventName] = [callback]} else  {// 存在则push到指定数组的尾部保存this.events[eventName].push(callback)}}// 触发事件的方法emit(eventName) {// 遍历执行所有订阅的事件this.events[eventName] && this.events[eventName].forEach(cb => cb());}// 移除订阅事件off(eventName, callback) {if (!this.events[eventName]) {return new Error('事件无效');}if (this.events[eventName]) {this.events[eventName] = this.events[eventName].filter(cb => cb != callback);}}// 只执行一次订阅的事件,然后移除once(eventName, callback) {// 绑定的时fn, 执行的时候会触发fn函数let fn = () => {callback(); // fn函数中调用原有的callback// 当第一次emit触发事件后在执行这一步的时候就通过 off 来移除这个事件函数, 这样这个函数只会执行一次this.off(eventName, fn);}this.on(eventName, fn);}
}// 测试
let em = new EventEmitter();
let workday = 0;
em.on("work", function() {workday++;console.log("work everyday");
});em.once("love", function() {console.log("just love you");
});function makeMoney() {console.log("make one million money");
}
em.on("money",makeMoney);let time = setInterval(() => {em.emit("work");em.off("money",makeMoney);em.emit("money");em.emit("love");if (workday === 5) {console.log("have a rest")clearInterval(time);}
}, 1000);

柯里化(知乎面试二面)

柯里化(currying) 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数。
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。柯里化不会调用函数。它只是对函数进行转换。
视频讲解
人类高质量JS函数柯里化

// 函数求和
function sumFn(...rest) {return rest.reduce((a, b) => a + b);
}
// 柯里化函数
var currying = function (func) {// 保存所有传递的参数const args = [];return function result(...rest) {// 最后一步没有传递参数,如下例子if(rest.length === 0) {return func(...args);} else {// 中间过程将参数push到argsargs.push(...rest);return result; // 链式调用}}
}// 测试
currying(sumFn)(1)(2)(3)(4)(); // 10
currying(sumFn)(1, 2, 3)(4)(); // 10// es6 实现
function curry(fn, ...args) {return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}// 第二种方式
function curry(func) { return function curried(...args) {  if (args.length >= func.length) {return func.apply(this, args);  } else {return function(...args2) {return curried.apply(this, args.concat(args2)); }   } };
}// test
function sum(a, b, c) {  return a + b + c;}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6,仍然可以被正常调用
alert( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化

一文带你搞懂JavaScript Currying(柯里化)函数

深拷贝deepCopy(面试)

function deepCopy(obj, cache = new WeakMap()) {// 数据类型校验if (!obj instanceof Object) return obj;// 防止循环引用,if (cache.get(obj)) return cache.get(obj);// 支持函数if (obj instanceof Function) {return function () {obj.apply(this, arguments);}}// 支持日期if (obj instanceof Date) return new Date(obj);// 支持正则对象if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);// 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了// 数组是 key 为数字的特殊对象const res = Array.isArray(obj) ? [] : {};// 缓存 copy 的对象,用于处理循环引用的情况cache.set(obj, res);Object.keys(obj).forEach(key => {if (obj[key] instanceof Object) {res[key] = deepCopy(obj[key], cache);} else {res[key] = obj[key];}});return res;
}// 测试
const source = {name: 'Jack',meta: {age: 12,birth: new Date('1997-10-10'),ary: [1, 2, { a: 1 }],say() {console.log('Hello');}}
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.ary[2] === source.meta.ary[2]);

附加:JSON.stringify深拷贝的缺点

  • 如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;
  • 如果obj里面有时间对象,时间将只是字符串的形式,而不是对象的形式;
  • 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
  • **如果对象中存在循环引用的情况也无法正确实现深拷贝,思路:**我们设置一个数组或者哈希表存储已拷贝过的对象,当检测到当前对象已存在于哈希表中时,取出该值并返回即可,如上。
  • // 例如: a:{b:{c:{d: null}}}, d=a, a 的深拷贝对象是 copy, 则 weakmap 里保存一条 a->copy 记录,当递归拷贝到d, 发现d指向a,而a已经存在于weakmap,则让新d指向copy
var test = {a: new RegExp('\\w+'),b: new Date(1536627600000),c: undefined,d: function() {},e: NaN};
console.log(JSON.parse(JSON.stringify(test)));
// 结果
// {
//     a: {},
//     b: "2018-09-11T01:00:00.000Z",
//     e: null
// }

链接
链接

instanceof(虾皮)

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

// 方法1 (只关心这个就行)
function isInstanceOf(instance, klass) {let proto = instance.__proto__;let prototype = klass.prototype;while (true) {if (proto === null) return false;if (proto === prototype) return true;proto = proto.__proto__;}
}// 测试
class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array))
// true true false// 方法2
function myInstanceof(left, right) {// 获取对象的原型let proto = Object.getPrototypeOf(left)// 获取构造函数的 prototype 对象let prototype = right.prototype; // 判断构造函数的 prototype 对象是否在对象的原型链上while (true) {if (!proto) return false;if (proto === prototype) return true;// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型proto = Object.getPrototypeOf(proto);}
}

Object.getPrototypeOf() 静态方法返回指定对象的原型(即内部 [[Prototype]] 属性的值)

const prototype1 = {};
const object1 = Object.create(prototype1);console.log(Object.getPrototypeOf(object1) === prototype1);
// Expected output: true

Object.create() 静态方法以一个现有对象作为原型,创建一个新对象。

const person = {isHuman: false,printIntroduction: function () {console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);},
};const me = Object.create(person);me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // Inherited properties can be overwrittenme.printIntroduction();
// Expected output: "My name is Matthew. Am I human? true"

instanceof mdn介绍

实现 jsonp

// 实现 jsonp 
// 动态的加载js文件
function addScript(src) {const script = document.createElement('script');script.src = src;script.type = "text/javascript";document.body.appendChild(script);
}
addScript(".js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});

实现prototype继承

// 实现prototype继承
// 所谓的原型链继承就是让新实例的原型等于父类的实例://父方法
function SupperFunction(flag1){this.flag1 = flag1;
}//子方法
function SubFunction(flag2){this.flag2 = flag2;
}//父实例
var superInstance = new SupperFunction(true);//子继承父
SubFunction.prototype = superInstance;//子实例
var subInstance = new SubFunction(false);
//子调用自己和父的属性
subInstance.flag1;   // true
subInstance.flag2;   // false

实现控制并发请求个数

js封装一个请求函数,可以5个并发请求,等其中一个结束后面的补上,直到结束。
链接
参考链接
链接

实现indexOf

实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。

const find = (S, T) => {if (S.length < T.length) return -1;for (let i = 0; i < S.length; i++) {if (S.slice(i, i + T.length) === T) return i}return -1;
}
// 待定
const find = (S,T) => S.indexOf(T)

手写call、apply、bind

call()、apply()、bind()这两个方法的作用可以简单归纳为改变this指向,从而让我们的this指向不在是谁调用了函数就指向谁。

每个JavaScript函数都是Function对象,Function对象是构造函数,而它的原型对象是Function.prototype,这个原型对象上有很多属性可以使用,比如说call就是从这个原型对象上来的。如果我们要模仿,必须在这个原型对象上添加和call一样的属性(或者说方法)。

// 三者的使用
var obj = {x: 81,
};var foo = {getX: function() {return this.x;}
}console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81

参考链接:
✅手写 实现call、apply和bind方法 超详细!!!
✅手写bind
视频讲解

// call方法实现
Function.prototype.myCall = function(context) {// 判断调用对象if(typeof this !== 'function') {console.error('type error');}// 判断call方法是否有传值,如果是null或者是undefined,指向全局变量windowcontext = context || window;// 获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组let args = [...arguments].slice(1);let result = null;// 获取调用call的函数,用this可以获取context.fn = this; // this指向的是使用call方法的函数(Function的实例,即下面测试例子中的bar方法)result = context.fn(...args); //隐式绑定,当前函数的this指向了context.// 将属性删除delete context.fn;return result;
}//测试代码
var foo = {name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {console.log(this.name);console.log(job, age);
}
bar.myCall(foo, 'programmer', 20);
// Selina 
// programmer 20
bar.myCall(null, 'teacher', 25);
// undefined
// teacher 25
call 和 apply 的区别是什么,哪个性能更好一些

call 比 apply 的性能好, 我的理解是内部少了一次将 apply 第二个参数解构的操作

// apply的实现
Function.prototype.myApply = function (context) {if (!context) {//context为null或者是undefined时,设置默认值context = typeof window === 'undefined' ? global : window;}context.fn = this;let result = null;if(arguments[1]) {// 第二个参数有值的话result = context.fn(...arguments[1]);} else {result = context.fn();}// 删除属性delete context.fn;return result;
}// 测试代码
var foo = {name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {console.log(this.name);console.log(job, age);
}
bar.myApply(foo, ['programmer', 20]);
// Selina programmer 20
bar.myApply(null, ['teacher', 25]);
// Chirs teacher 25
// bind方法
Function.prototype.myBind = function (context) {// 判断调用对象是否为函数if (typeof this !== "function") {throw new TypeError("Error");}// 获取参数const args = [...arguments].slice(1), fn = this;return function Fn() {// 根据调用方式,传入不同绑定值return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments)); }
}// 解析:
// 1、bind会返回一个函数
// 2、注意点:函数返回一个函数,很容易造成this的丢失
// 3、bind的实现,里面用到了上面已经实现的apply方法,所以这里直接复用
// 4、bind可以和new进行配合使用,new的过程会使this失效
// 5、三目运算符就是判断是否使用了new
// 6、this instanceof Fn的目的:判断new出来的实例是不是返回函数Fn的实例

es5 实现继承

待定

异步并发数限制

在这里插入代码片

异步串行 | 异步并行// 字节面试题,实现一个异步加法

在这里插入代码片

vue reactive

手写promise(一般情况下不会考,因为太费时间)

视频讲解
史上最最最详细的手写Promise教程

class MyPromise {static PENDING = "pending";static FULFILLED = "fulfilled";static REJECTED = "rejected";constructor(func) {this.status = MyPromise.PENDING; // 状态this.result = null; // 参数this.resolveCallbacks = [];this.rejectCallbacks = [];// 异常校验,为了兼容下面的代码:throw new Error('抛出失败');try {func(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject(error);}}resolve(result) {// resolve和reject函数是在函数的末尾执行的,所以加一层setTimeoutsetTimeout(() => {if(this.status === MyPromise.PENDING) {this.status = MyPromise.FULFILLED;this.result = result;this.resolveCallbacks.forEach(callback => {callback(result);});}})}reject(result) {// resolve和reject函数是在函数的末尾执行的,所以加一层setTimeoutsetTimeout(() => {if(this.status === MyPromise.PENDING) {this.status = MyPromise.REJECTED;this.result = result;this.rejectCallbacks.forEach(callback => {callback(result);});}})}// then函数有两个函数参数then(onSuccess, onError) {// 外层return promise的目的是为了完成链式调用return new MyPromise((resolve, reject) => {// then方法中的参数必须是函数,如果不是函数就忽略onSuccess = typeof onSuccess === 'function' ? onSuccess : () => {};onError = typeof onError === 'function' ? onError : () => {};// 如果then里面的状态为pending, 必须等resolve执行完之后在执行then, 所以需要创建数组,保留then里面的函数if(this.status = MyPromise.PENDING) {this.resolveCallbacks.push(onSuccess);this.rejectCallbacks.push(onError);}// 如果then方法执行的是成功的函数if(this.status === MyPromise.FULFILLED) {// 包裹setTimeout,解决异步问题,then放阿飞执行是微任务setTimeout(() => {onSuccess(this.result);});}// 如果then方法执行的是失败的函数if(this.status === MyPromise.REJECTED) {// 同上setTimeout(() => {onSuccess(this.result);});} })  }
}// 测试
console.log('第一步');
let promise1 = new MyPromise((resolve, reject) => {console.log('第二步');setTimeout(() => {resolve('这次一定');reject('下次一定');console.log('第四步');});// resolve('这次一定');// throw new Error('抛出失败');
});
promise1.then(result => {console.log(result)},err => {console.log(err.message)},
);
console.log('第三步');

数组扁平化

// 方案 1
function test(arr = []) {return arr.flat(Infinity);
}
test([1, 2, [3, 4, [5, 6]], '7'])// 方案 2
function reduceFlat(ary = []) {return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}// 测试
const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(reduceFlat(source))

对象扁平化

// 需求:
var output = {a: {b: {c: {dd: 'abcdd'}},d: {xx: 'adxx'},e: 'ae'}
}// 要求转换成如下对象
var entry = {'a.b.c.dd': 'abcdd','a.d.xx': 'adxx','a.e': 'ae'
}// 实现方案
function objectFlat(obj = {}) {const res = {};function flat(item, preKey = '') {Object.entries(item).forEach(([key, val]) => {const newKey = preKey ? `${preKey}.${key}` : key;if (val && typeof val === 'object') {flat(val, newKey);} else {res[newKey] = val;}})}flat(obj);return res;
}// 测试
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source)); // {a.b.c: 1, a.b.d: 2, a.e: 3, f.g: 2}

对象扁平化反转

图片懒加载

图片懒加载,手写见上面链接
原理:图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源 。根据这个原理,我们使用HTML5 的data-src 属性来储存图片的路径,在需要加载图片的时候,将data-src中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。

预加载:
预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。 通过预加载能够减少用户的等待时间,提高用户的体验。预加载的最常用的方式是使用 js 中的image对象,通过为image对象来设置 src 属性,来实现图片的预加载。
预加载则会增加服务器前端压力。

使用 setTimeout 实现 setInterval(待定)

// 使用 setTimeout 实现 setInterval
function mySetInterval(fn, timeout) {// 控制器,控制定时器是否继续执行var timer = {flag: true};// 设置递归函数,模拟定时器执行。function interval() {if (timer.flag) {fn();setTimeout(interval, timeout);}}// 启动定时器setTimeout(interval, timeout);// 返回控制器return timer;
}// 方法2
function mySetInterval() {mySetInterval.timer = setTimeout(() => {arguments[0]()mySetInterval(...arguments)}, arguments[1])
}mySetInterval.clear = function() {clearTimeout(mySetInterval.timer)
}mySetInterval(() => {console.log(11111)
}, 1000)setTimeout(() => {// 5s 后清理mySetInterval.clear()
}, 5000)


参考链接:

参考链接:前端面试出场率奇高的18个手写代码

其他手写代码:

1、手写获取数组的重复元素,要求尽可能用多种方法实现(小米)
3、用 promise 封装实现 readfile 和 writefile 的同步请求(百度)
4、 手写 Promise(字节、百度、深信服、小红书)
9、手写虚拟 dom 转换成真实 dom(字节)
10、手写 assign,要考虑全面,包括 symbol 也要考虑在内(猿辅导)
11、手写 ES6 的模板字符串(百度)
13、手写斐波那锲数列(知乎)

更多推荐

JavaScript手写题

本文发布于:2023-12-03 20:25:33,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1657281.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:JavaScript

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!