1、浅拷贝与深拷贝
1.1 浅拷贝
拷贝对象基本类型的值与拷贝对象引用类型的内存地址。
Object.assign()
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
展开运算符...
let obj1 = { name: 'Kobe', address:{x:100,y:100}};
let obj2= {... obj1};
Array.prototype.concat()
let arr = [1, 3, { username: 'kobe' }];
let arr2 = arr.concat();
Array.prototype.slice()
let arr = [1, 3, { username: ' kobe' }];
let arr3 = arr.slice();
1.2 深拷贝
拷贝对象基本类型的值与拷贝对象引用类型,新的对象处理引用类型的时候不会影响到被拷贝的对象。
JSON.parse(JSON.stringify())
let arr = [1, 3, { username: ' kobe' }];
let arr4 = JSON.parse(JSON.stringify(arr));
缺点是不能拷贝函数与正则表达式。
手写递归实现
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 如果对象里面引用了自己,则使用hash,避免循环递归。
if (hash.get(obj)) return hash.get(obj);
// 是对象的话就要进行深拷贝
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
1.3 参考资料
2、原型链
当调用对象的属性后,如果当前对象没有,则会通过原型链去查找该属性,直到没有找到为止,这个过程是自动的。
常用的原型链查找对象属性的流程
3、箭头函数与普通函数的区别
(1)箭头函数没有自己的this
对象。
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作 Generator 函数。
4、New操作符做了什么事情?
在 JavaScript 中,new
操作符用于创建一个对象实例。它执行以下步骤:
- 创建一个空对象。
- 将新对象的原型链接到构造函数的原型对象上。
- 将构造函数的作用域赋给新对象(因此
this
关键字指向新对象)。 - 执行构造函数,并将参数传递给它。
- 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象。
以下是一个示例,演示了 new
操作符的使用:
function Person(name, age) {
this.name = name;
this.age = age;
}
const john = new Person('John', 25);
console.log(john.name); // 输出: John
console.log(john.age); // 输出: 25
在上面的示例中,new Person('John', 25)
创建了一个新的 Person
对象实例,并将其赋值给 john
变量。
5、js编程
5.1 异步红绿灯
要求
红灯3s亮一次,绿灯1S亮一次,黄灯2S亮一次,如何让3个灯不断交替重复下去?
思路:
1、同步任务编写红绿灯代码
2、异步任务编写时间流程管理(关键)
3、同步任务递归调用异步任务(关键)
方法1、使用回调函数完成
// 编写同步任务
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
// 编写异步任务
function task(time,color,callback) {
setTimeout(function(){
if(color == 'red') {
red();
}
else if(color == 'green') {
green();
}
else if(color == 'yellow') {
yellow();
}
callback();
},time)
}
// 执行异步任务
function repetition(){
task(3000,'red',()=>{
task(2000,'green',()=>{
task(1000,'yellow',repetition)
})
})
}
repetition()
方法2、使用promise完成
// 编写同步任务
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
// 编写异步任务
function task(time,color) {
return new Promise((resolve,reject)=>{
setTimeout(function(){
if(color == 'red') {
red();
}
else if(color == 'green') {
green();
}
else if(color == 'yellow') {
yellow();
}
resolve();
},time)
})
}
// 执行异步任务
function repetition(){
task(3000,'red')
.then(()=>{
return task(2000,'green');
})
.then(()=>{
return task(1000,'yellow');
})
.then(repetition)
}
repetition()
方法3、使用async
// 编写同步任务
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
// 编写异步任务
function task(time,color) {
return new Promise((resolve,reject)=>{
setTimeout(function(){
if(color == 'red') {
red();
}
else if(color == 'green') {
green();
}
else if(color == 'yellow') {
yellow();
}
resolve();
},time)
})
}
// 执行异步任务
async function repetition(){
await task(3000,'red');
await task(2000,'green');
await task(1000,'yellow');
repetition()
}
repetition()
5.2 判断数组的最深深度
const getArrayDepth = (array) => {
if (Array.isArray(array)) {
return 1 + Math.max(...array.map(ele => getArrayDepth(ele)))
} else {
return 0
}
}
5.3 根据元素的parentId与id,将其合并为树数组
6、this指向
7、变量提升与作用域
额外的
let array = []
for(var i = 0; i < 3; i++) {
array[i] = function() {
console.log(i)
}
}
array[0]() // 3
array[1]() // 3
array[2]() // 3
8、闭包
闭包是指在一个函数内部定义的函数可以访问外部函数的变量,即使外部函数执行结束后,内部函数仍然可以访问外部函数的变量。
作用
- 封装,将变量或者函数封装在一个作用域内,防止其被外部访问和修改。
- 保存状态,内部函数可以访问外部函数的变量,因此可以通过闭包来保存变量的状态,如保存计时器。
- 模块化,避免了全局命名空间的污染
9、事件循环
10、Array的方法
developer.mozilla.org/zh-CN/docs/…
11、var与let、const
作用域
- var声明的变量,其作用域为整个函数,
- let、const声明的变量,其作用域为整个块级。
变量提升
- var声明的变量会被提升,也就是说,在函数中你使用这个变量可以在它的声明之前,它会被提升,值为undefind,不会报错。
- let、const不会被提升,使用该变量必须在声明之后。
var的一些业务常见问题
- 在纯净的script标签,var声明的变量会变成全局变量。
<!DOCTYPE html>
<html>
<head>
<title>Test let</title>
</head>
<body>
<script>
let globalLet = "I am a global let variable";
</script>
<script>
console.log(window.globalLet); // 输出: undefined
console.log(globalLet); // 输出: I am a global let variable
</script>
</body>
</html>
- 在将script标签设置为type="module"后,var声明的变量作用域只能存在于此标签中。
<!DOCTYPE html>
<html>
<head>
<title>Test module</title>
</head>
<body>
<script type="module">
var moduleVar = "I am a module var variable";
let moduleLet = "I am a module let variable";
const moduleConst = "I am a module const variable";
console.log(moduleVar); // 输出: I am a module var variable
console.log(moduleLet); // 输出: I am a module let variable
console.log(moduleConst); // 输出: I am a module const variable
</script>
<script>
console.log(window.moduleVar); // 输出: undefined
console.log(window.moduleLet); // 输出: undefined
console.log(window.moduleConst); // 输出: undefined
</script>
</body>
</html>
- 使用构建工具构建的js文件,当js文件用var声明了一个变量,那么它的作用域还是仅存在于此文件,它在构建构建里面会被打包成一个模块。
var myVar = "I am a var variable in a module";
console.log(myVar); // 输出: I am a var variable in a module
构建完成后
(function(modules) {
// Webpack引导代码...
var installedModules = {};
function __webpack_require__(moduleId) {
// 模块加载逻辑...
}
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
"./src/index.js": (function(module, exports) {
var myVar = "I am a var variable in a module";
console.log(myVar); // 输出: I am a var variable in a module
})
});