window.onload = function(){
//考核点: 作用域 this指向 预解析
var a =10;
function test(){
console.log(a);
a=100;
console.log(this.a);
var a;
console.log(a);
}
test();
}
//解析过程
//(1)预解析
var a;
function test(){};
//(2)逐行解析
a = 10;
test();
//(3)函数内部 预解析
var a;
//(4)逐行解析
console.log(a); //undefined
a=100;//重新赋值
console.log(this.a); //10
console.log(a); //100
//扩展 全局变量和局部变量同名的坑: 全局变量是不会作用于同名局部变量的作用域的
// (1)
// var a = 10;
// var a;
// console.log(a); //10
//(2)
var a = 10;
function fn(){
console.log(a); //undefined
var a;
};
fn()
//(3)
var a = 10;
function fn(){
a = 10; //修改全局变量的值
console.log(this.a); //10 this.a ==> window.a
};
fn()
//(4)
var a = 10;
function fn(){
var a = 100; //全局变量和局部变量同名的坑
console.log(this.a); //1 this.a ==> window.a
};
fn()
//(5)
var a = 10;
function test(){
console.log(a); //10
a=100;
console.log(this.a); //100
console.log(a); //100
}
test();
例题:
//1️⃣例题1:考核点 全局变量
function f(){
console .log( num );
num = 200;
console.log( num );
}
var num = 100;
f();
console.log(num);
// 解析过程:
//(1)预解析 var function(){ }
function f(){ //整个解析}
var num;
//(2)逐行解析
num = 100;
f();
//(3)函数执行
console.log( num ); //100
num = 200; //修改全局变量的量
console.log( num ); //200
console.log( num );//200
//例如:
console.log(a);
var a = 100;
//过程如下:(1)预解析
var a;
//(2)逐行解析
console.log(a);
a = 100;
//2️⃣例题2
function f(){
console .log( num );
var num = 200;
console.log( num );
}
var num = 100;
f();
console.log(num);
//解析过程:(1)预解析
function f(){}
var num;
//(2)逐行解析
num: = 100;
f();
//(3)函数执行解析
//(4)预解析
var num;
//(5)逐行解析
console.log( num ); //num同名了,这里的是局部变量,因此是undefined
num = 200;
console.log( num ); //200
console.log( num ); //全局变量 100
//例题解析:
var a = 100;
var a;
console.log(a); //100
//规则: 全局变量和局部变量同名时,全局变量是不会作用于局部变量的作用域的
var a = 100;
function f(){
var a;
console.log(a) //undefined
};
f();
//3️⃣例题3:
function f (num){
console.log( num );
var num = 200;
console.log( num );
}
var num = 100;
f( num );
console.log( num );
//解析过程:(1)预解析
function f(){}
var num;
//(2)逐行解析
num: = 100;
f(num);
var num = 100;
//(3)⭕如果有形参,先给形参赋值
//(4)函数执行解析
//(5)预解析
var num;
//(6)逐行解析
console.log( num ); //100
num = 200;
console.log( num ); //200
console.log( num ); //全局变量 100
//4️⃣例题4: this,apply,arguments
var x = 1;
function f( y ) { //函数声明
return this.x + y
}
var obj = {
x: 2
}
var f2 = function() { //函数表达式
return f.apply( obj, arguments) //arguments 是f2(3)传进来的参数3 作为一个集合
// 且f在obj中执行,因此this指向了obj中的x: 2,且y又是3
}
var z = f2(3);
console.log(z); // 5
(1)call() 方法接收一个目标对象和一组参数,将函数内部的 this 指向目标对象,并传入参数列表。
function greeting() { console.log(`Hello, ${this.name}!`); } const person = { name: 'Alice' }; greeting.call(person); // 输出:Hello, Alice!
(2)apply() 方法与 call() 方法类似,也是将函数内部的 this 指向目标对象,但是它接收一个目标对象和一个参数数组,而不是一组单独的参数。
function greeting(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: 'Bob' }; greeting.apply(person, ['Hi', '!']); // 输出:Hi, Bob!
call() 和 apply() 方法的作用非常相似,只是传递参数的方式有所不同。通常情况下,我们可以根据具体的需求来选择使用它们中的任意一个。如果要传递的参数已经存在于一个数组或类似数组的对象中,那么使用 apply() 会更加方便;如果需要手动指定每一个参数,那么使用 call() 可能更为自然。
//apply() call()
function Person(name, age){
this .name = name;
this .age = age;
}
var p1 = new Person('p1',18);
var p2 = {}; //对象
//person对象在p2对象中执行,作用域发生改变
// Person.apply(p2,['p2',19]); //通过数组方式传递值
Person.call(p2,'p2',19);
console.log(p2)
//5️⃣例题5 异步任务中的微任务 宏任务
//JS是单线程的
setTimeout(()=>{ //异步任务 宏任务
console.log(1)
},100)
setTimeout(()=>{ ///异步任务 宏任务
console.log(2)
},0);
console.log(3) //同步任务
let p = new Promise((resolve, reject) =>{
resolve(4);
console.log(5); //同步任务
});
p.then(res => {
console.log(res) //异步任务 微任务
})
console.log(6) // 同步任务
//例如:
setTimeout(()=>{
console.log(0) //异步任务 宏任务
}.100)
console.log(1); //同步任务
let p = new Promise((resolve, reject) => {
//resolve(4);
console.log(3); //同步任务
});
p.then(res=>{
console.log(res)// 异步任务 微任务
})
console.log(2); //同步任务
//1 3 2 4 0
//6️⃣例题6: await 关键字
function f2(){
console.log(1);
setTimeout(()=>{
console.log(2)
},100)
}
async function f(){
console.log(3)
await f2();
console.log(4)
}
f();
console.log(5);
Promise 是 JavaScript 中的一种异步编程方式,它用于管理异步操作的状态,可以更加优雅地处理回调函数嵌套的问题。一个 Promise 表示一个异步操作的最终完成或失败,并返回一个包含异步操作结果的值。(同步容易导致堵塞)
Promise 有以下三种状态:
- pending:表示 Promise 实例初始化后的初始状态,既不是成功(fulfilled)也不是失败(rejected)状态。
- fulfilled:表示 Promise 实例已经成功地完成了异步操作并返回了值。
- rejected:表示 Promise 实例已经遇到了错误并返回了错误原因。
Promise 支持链式调用,可以把多个异步操作串联在一起依次执行,使用 .then() 方法和 .catch() 方法来处理 Promise 实例的状态变化。.then() 方法用于处理 Promise 实例从 pending 状态到 fulfilled 状态时的回调函数,.catch() 方法用于处理从 pending 状态到 rejected 状态时的回调函数。
//promise 解决JS中回调难以维护和控制 类似一个容器或对象 var id = 1; $.ajax({ url:'', data:{}, success:function(res){ //res id = res.id; $.ajax({ //回调地狱 url:'', data:{}, success:function(res){ //res id = res .id; } }); } }); console.log(id) var p = new Promise(function(resolve,reject){ resolve();//异步操作成功的结果 reject();//异步操作失败的结果 }); //promise实例生成后,可以通过then方法指定成功或失败状态 的回调函数 p.then(function(res){ //res =>异步操作成功的结果 },function(err){ //err =>异步操作失败的结果 });
宏任务(macro task)和微任务(micro task)
宏任务指的是传给 JavaScript 引擎的任务队列中的任务,而微任务指的是微任务队列中的任务。
(1)常见的宏任务包括:
- setTimeout、setInterval、setImmediate
- I/O 操作、UI 渲染
- 跨域通信、postMessage、MessageChannel
(2)常见的微任务包括:
- Promise.then、catch、finally, async, await
- Object.observe、MutationObserver
- process.nextTick
在每次事件循环中,当当前宏任务执行完毕后,JavaScript 引擎会立即处理所有微任务。当所有微任务都被处理完毕后,才会进入下一个宏任务。在 JavaScript 中,微任务的优先级比宏任务高,所以微任务总是会在下一个宏任务前执行完毕。
await 关键字
await 关键字用于暂停异步函数的执行,等待一个 Promise 对象 fulfilled(或者 rejected)后继续执行异步函数并返回 Promise 对象的值。可以将 await 视为一个让出线程的标志,它告诉 JavaScript 引擎在等待 Promise 返回结果时暂停执行该函数的代码,直到 Promise 完成或被拒绝。
使用 await 关键字可以简化异步代码的编写,避免了回调地狱和复杂的 Promise 链式调用。
运行一个函数时,有没有调用者? 没有调用者,默认指向全局window,严格模式下undefined 。有调用者,指向调用者
window.onload = function(){
fn(); //无调用者
obj.fn(); //有调用者
//情况一:
var length = 1
function fn() {
console.log( this.length ) //this.length ===> window.length
}
fn(); //无调用者 为1
//情况二:
var length = 1;
function fn() {
console.log( this.length ) //this.length ===> obj.length
};
var obj = {
length: 100,
f1:fn
}
//obj.f1(); //100 有调用者
var x = obj.f1;
x(); //1 无调用者 this.length ===> window.length
//情况三:
var length = 1;
function fn(){
console.log( this.length ) //this.length ===> arr.length
};
var lists = [fn,2,3,4,5,6];
lists[0](); //有调用者, 调用者为arr 即输出6
var f = list[0];
f(); // 输出1
}
阿里面试题✍
window.onload = function(){
//阿里面试题 考核点: this指向 arguments ES6扩展运算符
var length = 1;
function fn(){
console.log(this.length)
}
var obj ={
length: 100,
action: function(callback){
callback();
//callback() == > fn() callback( )没有调用者,指向全局。拿到1
arguments[0]();
//arguments[0]==>fn
//arguments[0]() ==>fn() this.length==>arguments .length
}
}
var arr =[1,2,3,4];
obj.action(fn, ···arr); // 1和5
}
用来访问传递给函数的参数列表。 arguments对象具有以下方法:
⭕箭头函数没有自己的 arguments 对象
在ES6中引入了剩余参数(rest parameters)语法,它允许我们将函数的多个参数表示为一个数组。与arguments对象不同,剩余参数是真正的数组,它可以使用数组方法,并且可以在函数定义时声明。
//写一个函数 findMax() 接受任意数量的数字参数,并返回其中最大的那个数。要求使用剩余参数语法。
function findMax(...numbers) {
//使用 ...numbers 的语法来接收任意数量的数字参数
let max = Number.NEGATIVE_INFINITY;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
//遍历输入数组,如果当前数字比 max 大,则将其替换为新的最大值
}
}
return max;
}
console.log(findMax(1, 5, 2, 7, -3)); // 输出 7
console.log(findMax(100, 200, 150)); // 输出 200
在ES6之前,通常使用
-Infinity
代替Number.NEGATIVE_INFINITY(
表示负无穷大)。从ECMAScript 2015 (ES6)开始,Number.MIN_SAFE_INTEGER
和Number.MAX_SAFE_INTEGER
也被添加到JavaScript中,分别表示安全的最小和最大整数。
在 JavaScript 中,函数可以通过函数声明或函数表达式的方式来定义。
(1)函数声明:使用 function 关键字声明一个函数,通常出现在代码的顶部,可以在函数声明之前调用它们。
function add(x, y) {
return x + y;
}
(2) 函数表达式:将一个函数赋值给变量,函数表达式不会被提升(hoist),只能在赋值之后使用。
const add = function(x, y) {
return x + y;
};
函数表达式还有两种变体:
2.1 命名函数表达式(Named Function Expression,NFE):函数表达式中指定一个函数名,可以在函数内部以及函数之后引用它自身。
const add = function sum(x, y) {
return x + y;
};
console.log(add(1, 2)); // 输出:3
console.log(sum(1, 2)); // 报错:sum is not defined
2.2 箭头函数表达式:使用箭头符号 => 来定义一个函数,它是匿名函数,并且不能作为构造函数使用。
const add = (x, y) => x + y;
⭕通常情况下,函数表达式更加灵活,可以用来创建匿名函数或者作为函数参数传递,而函数声明更适合用于定义顶级函数(即不在其他函数内部定义的函数)。
//面试题2:作用域的坑
var a = 10;
var obj = {
a:99,
f:test
};
function test(){
console.log(a); //undefined
a=100;
console.log(this.a); //99
var a;
console.log(a); //100
}
obj.f();
/美团面试题
//考核点: 作用域 预解析
var a = 10;
function f1(){
var b = 2 * a;
a = 20;
var c = a+1;
console.log(b);
console.log(c);
}
f1()
//解析过程
//(1)预解析
var a;
function f1(){} //整个函数,并没有调用
//(2)逐行解析 由上而下
a = 10;
f1();
//(3)f1函数 预解析 (全局变量与局部变量是否同名)
var b;
var a;
var c;
//(4) f1函数 逐行解析
b = 2*a;
a = 20;
c = a+1;
console.log(b); //2*undefined = NaN
console.log(c); //21
ECMAScript 6.0 (以下简称 ES6) 是JavaScript 语言的下一代标准,已经在 2015 年 6月正式发布了它的目标,是使得JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript与JS的关系
ECMAScript和Javacript 的关系是,前者是后者的规格,后者是前者的一种实现 (另外的ECMAScript方言还有JScript 和ActionScript) 。日常场合,这两个词是可以互换的。
Babel 转码器
Babel是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5代码,从而在老版本的浏览器执行。这意味着,可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。
//转码前
var $ = name => document .querySelector(name);
//转码后
var $ = function(name){
return document .querySelector(name)
}
window.onload = function(){
var a = 1;
var b = 2;
var c = 3;
var a = 1,b = 2,c = 3;
//ES5中的为变量赋值,只能直接指定值
//数组解构
let [x,y,z] = [1,2,3];
//这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
var [a,b,c] = [1,2];
//a = 1; b = 2 ; c = undefined
//即是ES5中的 var a = 1, b = 2,c;
//ES5
var id = 1;
var arr = [1,2,3];
var obj = (title:'obj'};
var flag = true;
//ES6 即为
var [id,arr,obj,flag] = [1,[1,2,3],[title:'obj'},true];
var[a,[b,d],c] = [1,[2,3],4];
console.log(b,d,c); // 2 3 4
//如果解构不成功,变量的值就等于undefined.
//设置默认值 —— 解构赋值允许指定默认值
let arr = [88,99];
var a = arr[0] || 1; //没有时默认为1
var b = arr[1] || 1;
//ES6 写法
var [a=1,b=1] = arr;
var [a=1,b=1] = [10,30];
var [a=1,b=1] = [null,undefined]; //a = nul1, b = 1
//undefined 时默认值是生效的
//只有当一个数组成员严格等于undefined,默认值才会生效
console.log(a,b);
//特殊
function fn(){
return 100
};
var [a=1,b=fn()] = [10,20];
console.log(b); //20 惰性函数求值:默认值生效了才会执行函数
//但是已经有匹配的值所以默认值的函数并不会被执行
}
这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象解构的写法
//对象解构的写法:
let [a,b] = {b:1,a:2};
//原本写法
const obj = {
title:'obj',
age:18,
id:1,
result:[10,20,30]
};
var title = obj.title;
var id = obj.id;
var result = obj.result;
//对象解构处理 位置无所谓,而可以有别名
//即result:res = 匹配模式 ===>变量名
var {id,title,result:res} = obj;
console.log(res);
别名 + 默认值
变量名与属性名不一致,name是匹配模式,n是变量
var [name:n, age:a] = [name:'123', age:18];
//解构赋值允许指定默认值
var (id,title:t,result:res=[1,2]} = obj;
//特殊的 将对象 {x:100} 的属性 x 的值赋给变量 x
var x;
({x}= {x:100});
在括号中使用了花括号的语法来表示一个对象字面量,而不是代码块。
接着,在等号的左侧使用了对象解构的语法,指定了要从右侧对象中提取的属性 x。
由于对象解构的语法需要将整个表达式放在圆括号中
因此整个表达式需要用一对空圆括号进行包裹。
//根据解构赋值的规则,变量名与对象属性名相同的情况下才会发生赋值。
var {id=1,result:res=2} = {result:88,id:100,title:99};
console.log(id,res); //100 88
//将obj对象中的id和result数组的值累加
function sum({id=1,result:arr=[]}){
return arr.reduce((total, cur)=>total+cur, id);
}
console.log(sum(obj)); //61
//默认值生效的条件是,对象的属性值严格等于 undefined
let {x:a = 10,y:b = 20} = {x: undefined};
//解构赋值表达式左侧的对象属性 a 和 b 都设置了默认值
//右侧对象包含 x 属性,但其值为 undefined,则 a 的值会被设置为 10,b 的值会被设置为 20
//⭕这里的默认值只针对对象属性值为 undefined 的情况
//‼️非严格相等(即 ==)的情况不会触发默认值
let {x:a = 10, y:b = 20} = {x: null};
// a 和 b 的值都不会被设置为默认值,而是分别为 null 和 20。
JS的reduce方法是用于对数组元素进行累计计算的高阶函数。它可以将数组中的每一个元素依次传入一个回调函数中,并在每次传入后将回调函数的返回值进行累计操作,最终得到一个单一的结果。
Reduce方法接收两个参数:一个回调函数和一个可选的初始值。回调函数有两个参数:累计值(也称为累加器)和当前元素值。在每次调用回调函数时,累计值都会被更新为上一次回调返回的值。
该方法的调用方式为:array.reduce(callback[, initialValue])。如果提供了初始值,则累加器的初始值为该值;否则,累加器的初始值为数组第一个元素。如果数组为空且未提供初始值,则会抛出错误。
使用reduce方法可以非常方便地实现对数组元素的求和、求积、字符串连接等操作。
相等运算符包括严格相等运算符(===)和非严格相等运算符(==)。
(1)严格相等运算符会比较两个值的类型和值是否完全相等。只有当两个操作数的类型和值都相同时,才会返回 true。‼️不同的对象即使具有相同的属性和值,它们也不是严格相等的,因为它们是不同的引用。
1 === 1 // true
"hello" === "hello" // true
null === null // true
let obj1 = {a: 1};
let obj2 = {a: 1};
obj1 === obj2 // false
(2)非严格相等运算符通常可以进行类型强制转换,并将两个操作数转换为相同的类型,然后再进行比较。这种转换可能会导致一些意想不到的行为,因此通常建议使用严格相等运算符。
"1" == 1 // true (字符串 "1" 被转换为数字 1)
null == undefined // true (它们被认为相等)
"true" == true // true (字符串 "true" 被转换为布尔值 true)
‼️在使用非严格相等运算符时,如果一个操作数是 null 或 undefined,则会发生类型转换,而另一个操作数也必须是 null 或 undefined 才会返回 true。
null == null // true
undefined == undefined // true
null == undefined // true
0 == null // false (0 不是 null 或 undefined)
"" == null // false (空字符串不是 null 或 undefined)
是一种编程技巧,它延迟计算表达式的值,直到真正需要时才进行计算。这种技巧可以优化程序的性能,并减少不必要的计算和内存占用。
在JavaScript中,可以使用惰性求值来避免重复计算某个值或执行某个函数。
使用闭包缓存结果是一种常见的惰性求值技巧。它通过创建一个闭包,将结果保存在闭包中,并在下一次调用时返回已缓存的结果,而不是重新计算。
例如,以下代码使用闭包缓存getScrollTop()
函数的结果,在第一次调用后返回已缓存的结果,从而提高了性能:
var getScrollTop = (function() {
var scrollTop; // 定义变量保存结果
return function() {
if(scrollTop !== undefined) { // 如果结果已经存在,则直接返回
return scrollTop;
}
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; // 计算结果
return scrollTop; // 返回结果
}
})();
console.log(getScrollTop()); // 第一次调用,计算并返回结果
console.log(getScrollTop()); // 第二次调用,返回已缓存的结果
惰性函数是一种只有在需要时才被执行的函数。它通常用于处理特定的浏览器或环境差异,并根据需要加载相关代码。
例如,以下代码使用惰性函数来检测浏览器是否支持addEventListener( )
方法,并在需要时加载相应的代码:
var addEvent = function(elem, eventType, handler) {
if(elem.addEventListener) { // 如果浏览器支持addEventListener()方法,则直接调用
elem.addEventListener(eventType, handler, false);
} else if(elem.attachEvent) { // 如果浏览器支持attachEvent()方法,则添加适当的前缀并调用
elem.attachEvent('on' + eventType, handler);
} else { // 如果浏览器都不支持,则返回null
elem['on' + eventType] = handler;
}
};
var addEventIE = function(elem, eventType, handler) {
elem.attachEvent('on' + eventType, handler);
};
var addEventW3C = function(elem, eventType, handler) {
elem.addEventListener(eventType, handler, false);
};
var addEventLazy = function(elem, eventType, handler) {
if(window.addEventListener) { // 如果浏览器支持addEventListener()方法,则直接调用
addEventLazy = addEventW3C;
} else if(window.attachEvent) { // 如果浏览器支持attachEvent()方法,则添加适当的前缀并调用
addEventLazy = addEventIE;
} else { // 如果浏览器都不支持,则返回null
addEventLazy = null;
}
addEventLazy(elem, eventType, handler); // 调用相应的函数
};
addEventLazy(document.body, 'click', function(){ console.log('click'); }); // 第一次调用,检测浏览器支持情况并执行相应的函数
addEventLazy(document.body, 'mouseover', function(){ console.log('mouseover'); }); // 第二次调用,直接执行已缓存的函数
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值
set 本身是一个构造函数,用来生成 Set 数据结构
var arr = [1,2,3];
const s1 = new Set();
//s1.add(1);
//s1.add(2);
//s1.add(3);
//s1.add(1) .add(2) .add( 3 ) .add(2) .add(1) ;
s1.add(1) .add(2) .add(3 ) .add(2) .add( '1' );
console.log(s1);//set(3) { 1, 2,3 }
//set(3) { 1, 2, 3,'1' };
//Set 构造函数会根据这个可迭代对象中的每个元素创建一个新的 Set 对象
let mySet = new Set([1, 2, 3]);
{
let mySet = new Set(['a', 'b', 'c']);
console.log(mySet); // 输出 Set(3) {"a", "b", "c"}
}
console.log(mySet); // 输出 Set(3) {1, 2, 3}
//在大括号内部使用 let 关键字定义的变量 mySet 只在该代码块内有效,不影响外部作用域中的同名变量。
//⭕只有在需要在代码块中定义临时变量、常量或函数等时才需要使用大括号,否则可以直接省略。
let mySet = new Set();
mySet.add(1).add(2).add(3);
// 等同于
let mySet = new Set([1, 2, 3]);
//简洁定义
const s2 = new Set([1, 1, 2, 3, 2,1,2,3,2]);
//数组进行去重
var arr = [1,2,3,2,1,2,3,2,1];
const s3 = new Set(arr);
console.log(s3); // set(3){ 1,2,3 }
//类型的转换
//1、Array.from();
console.log(Array.from(s3));//[ 1, 2, 3 ]
//2、...扩展运算符
console.log([...s3]); //[ 1, 2,3 ]
练习题✍
var items =[10, 34,26,64, 24,34, 26,44, 67,24,67,44,78,26,34, 67, 78, 34];
//求出大于50的数据且去重的处理
//分析
//1、求出大于50
//let filterNum = items .filter(v=>v>50);
//2、去重
//let s5= new Set(filterNum);
//3、类型转换
// let a = [...s5];
//4、代码合并
//求出大于50的数据且去重的处理
console.log([...new Set(items .filter(v=>v>50))]);
//[ 64,67, 78 ]
定义
//map的数据结构
const m = new Map() ;
//链式写法
m. set('name' , 'value ') . set('age' ,18) . set('a' , 'a') ;
console.log(m);
// Map(3) { 0: {"name" => "value "} 1: {"age" => 18} 2: {"a" => "a"} }
//这里的set 表示map的添加方法
var a = {id:2};
m. set(a, 'value') ;
const m1 = new Map();
m1.set('title', 'hello') .set( 'id',1);
//console.log(m1);
//Map(2){ 'title' => 'hello', 'id' => 1}
const msg = {name: 'tom'};
m1. set(msg,'abc');
console.log(m1);
//Map(3) { 'title' => 'hello','id'=> 1, { name:'tom' } =>'abc' }
//简洁写法
const m2 = new Map([['a',1],['b',2],['c',3]]);
//⭕获得键值对对比
//方式一:
var obj = {
a:1,
b:2,
c:3
};
//求出obj对象的key和value
//0bject.keys(obj);
for(var k in obj){
console.log(k)//abc
console.log(obj[k]); //123
}
//方式二:
for(let [k,v] of m2){ // [k,v] 相当于解构赋值
console.log(k); //abc
console.log(v); //123
}
常见面试题✍
//常见面试题
const m3 = newMap([['a',l],['b',2],['c', 3]]);
//1、map结构转为object
var o = { };
for(let [k,v] of m3){
o[k] = v;
}
console.log(o) //{ a: 1, b: 2,c: 3}
//2、object转为map结构
var obj = {
a:1,
b:2,
c:3
}
const m4 = new Map();
for(var k in obj){
m4.set(k,obj[k])
}
// ⭕ const m4 =new Map(Object.entries(obj));
console.log(m4); //Map(3) { "a"=> 1,"b"=> 2,"c"=> 3 }
const lists = [{count:10,name: 'a'},{count:45,name: 'b'},{count:27,name: 'c'},{count:5,name: 'd'}]
//3、过滤掉count为3和3的倍数,进行去重处理
const lists = [{count:10,name: 'a'},{count:45,name: 'b'},{count:27,name: 'c'},{count:5,name: 'd'}];
const filteredLists = lists
.filter(item => item.count % 3 !== 0)
.map(item => ({name: item.name, count: item.count}))
const uniqueLists = Array.from(new Set(filteredLists.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
console.log(uniqueLists);
// 输出结果: [{count: 10, name: 'a'}, {count: 45, name: 'b'}, {count: 5, name: 'd'}]
//JSON.stringify() 将每个对象转换成字符串形式,以便于 Set 进行去重
//(1)使用 Array.filter() 方法过滤出 count 不为3及其倍数的元素。
//(2)使用 Array.map() 方法将剩余的元素转换成 {name, count} 格式的新对象。
//(3)使用 Set 数据结构去重后的新对象数组。
//(4)将去重后的结果转回到普通数组形式,并返回它。
Object.entries( ) 用于将一个对象转换为键值对数组。这个方法返回一个数组,其中包含了指定对象中所有可枚举属性的键值对(即 [key, value] 数组)。
//对一个对象进行遍历和操作 const object1 = { a: 'somestring', b: 42 }; for (const [key, value] of Object.entries(object1)) { console.log(`${key}: ${value}`); } // 输出结果: 'a: somestring' 和 'b: 42'
使用 for...of 循环遍历 object1 对象的键值对数组,将每个键值对保存到 [key, value] 数组中。在循环的每一次迭代中,打印出键和值的信息。
作用域是指程序源代码中定义的范围
作用域规定了如何设置变量,也就是确定当前执行代码对变量的访问权限
JavaScript采用词法作用域,也就是静态作用域
var value = 1;
function foo() {
console.1og(value); //1
}
function bar() {
var value = 2;
foo();
}
bar(); // 1
变量对象是当前代码段中,所有的变量(变量函数形参 arguments )组成的一个对象
变量对象是在执行上下文中被激活的,只有变量对象被激活了,在这段代码中才能使用所有的变量
变量对象分为全局变量对象和局部变量对象
全局简称为 Variable Object VO; 函数由于执行才被激活称为 Active object AO(以上面代码为例)
在JS中, 函数存在一个隐式属性[[scopes]], 这个属性用来保存当前函数的执行上下文环境,由于在数据结构上是链式的,因此也被称作是作用域链, 我们可以把它理解为一个数组
可以理解为是一系列的AO对象所组成的一个链式结构
function a(){ }
console.dir(a) // 打印结构
function a(){
console.dir(a)
}
a( )
因此可以得出一个结论: [[scopes]] 属性在函数声明时产生在函数调用时更新即: 在函数被调用时,将该函数的AO对象压入到[[scopes]]中
作用域链的作用
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的
最直观的表现就是:
var a = 100
function fn(){
console.1og(a)
}
fn() // 100
function fn(){
var a = 100
}
fn()
console.1og(a) // a is not defined
✍画出以下代码执行的作用域链
var global
function a(){
//AO
var aa = 123
function b() {
//AO
var bb = 234
}
b()
}
a()
①a函数生成
②a函数被调用
③b函数生成 (a函数调用导致b函数生成)
④b函数调用
函数执行完就销毁所谓销毁就是断掉图中的线。
思考 a函数调用和b函数生成是不是同一个作用域链?
var global
function a() {
// 到这个里面来了
var aa = 123
// b函数定义
function b() {
// 到这里面来了
var bb = 234
aa = 0
}
// b执行
b()
console.1og(aa);
}
// a执行
a()
// 打印aa的结果 如果是00 说明是同一个 没有分离
定义:
//例如:
var global
function a() {
// 到这个里面来了
var aa = 123
// b函数定义
function b() {
conso1e.1og(aa);
}
return b
}
// a执行
var res = a()
res()
window.onload = function(){
//闭包是能够读取其它函数内部变量的函数
//是“定义在一个函数内部的函数”,将函数内部和函数外部连接起来
var a = 10;
function f1(){
var a = 109;
function f2()[
console.log(a)
};
f2();
};
f1();
function f1(){
var a = 100;
return function f2()[
console.log(a)
};
};
f1()();
//目的就是为了获取局部变量a
function f1(){
var a = 100;
return a;
};
f1();
//(1)
var a = 10; //全局变量 存在window对象下
function f1(){
a++;
console.log(a)
}
f1(); //11
f1(); //12
//(2)
function f1(){
var a = 10; //局部变量
a++;
console.log(a)
};
f1(); //11
f1(); //11
//总结:局部变量无法共享和长久的保存,而全局变量可能造成变量污染,
//闭包:既可以长久的保存变量又不会造成全局污染。
//闭包的写法
function f1(){
var a = 10; //局部变量
function f2(){
a++;
console.log(a)
};
return f2
};
var f = f1(); // f1()执行的结果就是闭包
//返回的是一个函数,并且这个函数对局部变量存在引用,形成了闭包的包含关系
f(); //11
f(); //12
function f1(){//局部变量
var a = 10;
return function(){
a++;
console.log(a)
};
};
var f = f1();
f(); //11
f(); //12
var x = f1();
x(); //11
x(); //12
x(); //13
}
function add(num1, num2, ca1back) {
var sum = numl + num2;
if (typeof cal1back ==='function') {//类型判断,确认是一个函数
ca11back(sum);
}
}
add(1,2,function (sum) {
console.log(sum);
})
// 是一个闭包吗?
// 含义: 能够读取其他函数内部变量的函数。
手写实现 bind
// getName 的 this指向foo对象
let foo = {
name:'ji11'
}
function getName () {
consoTe.1og(this.name)
}
Function.prototype.myBind = function (obj) {
// 将当前函数的this 指向目标对象
let _self = this
return function () {
return _self.ca11(obj)
}
}
let getFooName = getName.myBind(foo)
getFooName() // ji11
防抖函数可以让某个函数在一定时间内多次触发时,只执行最后一次触发的函数。当调用 debounce 返回的函数时,如果上一次调用还没有到达时间间隔,就会清除上一次的计时器,然后重新开始计时,直到时间间隔到了才会执行传入的函数。
/**
* 防抖函数
* @param {Function} fn - 要执行的函数
* @param {Number} delay - 时间间隔
*/
function debounce(fn, delay) {
let timer;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
当持续触发事件,一定时间内没有再触发事件,事件处理函数才会执行一次如果设定的时间到来之前,又一次触发了事件,就重新开始延时
触发事件,一段时间内,没有触事件执行,肯定是定时器
(在设定的时间内 又一次触发了事件 重新开始延时 代表的就是重新开始定时器)
(那么意味着上一次还没有结束的定时器要清除掉 重新开始)
在JavaScript中,debounce是一种常用的技术,用于限制某个函数反复触发的频率。这种技术通常使用一个计时器和一个中间函数来实现。当需要限制函数的调用频率时,可以将该函数传递给中间函数,并指定一个时间间隔(例如500毫秒)。当用户触发事件时,计时器会启动并开始倒计时。如果在指定的时间内再次触发事件,则计时器将重新开始计时。只有在计时器完全结束后才会调用传入的原始函数。这样可以确保函数仅在用户完成操作或停止输入一段时间后才被调用,从而提高性能和用户体验。
应用场景:
ar input = document.getElementById('input')
// 防抖的函数
function debounce(delay,callback) [
let timer
return function(value){
clearTimeout(timer)
//我们想清除的是setTimeout 我们应该存储这个timer的变量
//timer变量需要一直保存在内存当中
//不想立即打印之前已经输入的结果 清除以前触发的定时器
//存储这个timer的变量一直要在内存当中——>内存的泄露 <——闭包
//闭包:函数里面return出函数
timer = setTimeout(function () {
//console.log(value) 不在函数内输出,而是在外面输出
callback(value)
},delay)
}
}
function fn(value){
cnsole.log(value)
}
var debounceFunc = debounce(1000,fn)
//首先 输入框的结果只出现一次 是在我键盘抬起不再输入后的1秒后输出
input.addEventListener('keyup', function (e){
debounceFunc(e.target.value)
})
debounceFunc 通常是指一个函数防抖的实现。所谓函数防抖(Debouncing)就是在函数被频繁调用时,只有等到某个连续时间段内没有新的调用发生时,才真正去执行该函数。这样可以避免函数被频繁调用导致性能问题或者其他不必要的行为。
实际的应用
改变浏览器宽度的时候,希望重新渲染使用Echarts时,Echarts的图像,可以使用此函数,提升性能。 (虽然Echarts里有自带的resize函数)
典型的案例就是输入搜索: 输入结束后n秒才进行搜索请求,n秒内又输入的内容,就重新计时解决搜索的bug
节流函数可以让某个函数在一定时间间隔内只执行一次。当调用 throttle 返回的函数时,如果前一次调用距离这一次调用的时间还没有到达时间间隔,就不会执行传入的函数。只有等到时间间隔到了,才会执行传入的函数。
/**
* 节流函数
* @param {Function} fn - 要执行的函数
* @param {Number} delay - 时间间隔
*/
function throttle(fn, delay) {
let timer;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
即 当持续触发事件的时候 保证一段时间内 只调用一次事件处理函数段时间内 只做一件事情。
(1)什么是 JS 节流函数?它有什么作用?
JS 节流函数是一种在特定时间间隔内执行某个函数的技术,可以有效地控制函数的执行速率并避免过度消耗计算资源。节流函数将某个函数的执行按照固定时间间隔进行划分,每隔一段时间便执行一次,从而避免了频繁触发的消耗和重复计算。常见的应用场景包括用户输入、页面滚动、鼠标移动等。
(2)请实现一个 JS 节流函数。
function throttling(func, delay) {
let lastTime = 0;
return function(...args) {
const nowTime = new Date().getTime();
if (nowTime - lastTime > delay) {
func.apply(this, args);
lastTime = nowTime;
}
}
}
(3)请说明节流实现原理,并与防抖进行比较。
JS 节流的实现原理是每隔一定时间后执行一次函数。其原理是通过记录上一次函数执行的时间戳和当前时间戳之差来判断是否需要执行函数。当两者之差大于设定的时间间隔时,执行函数并更新时间戳。如果两者之差小于设定时间间隔,则不执行函数,等待下一定时器时间到来再次尝试执行函数。
在功能上,节流和防抖都可以用于控制某个函数的执行,从而避免性能问题和其他影响。但是,它们的实现方式略有不同。防抖是在连续的多次触发过程中,只有最后一次触发的函数最终被执行;而节流是在连续的多次触发过程中,每隔一段时间执行一次函数,而未到达时间间隔的其他触发则不会执行。
保证一个类只有一个实例,并提供一个访问它的全局访问点。
/**
* 单例模式
*/
const Singleton = (function() {
let instance;
function Singleton() {
if (!instance) {
instance = this;
}
return instance;
}
return Singleton;
})();
//每次创建新对象时,会先检查是否已经存在实例,如果已经存在就返回实例,
//否则就创建一个新实例并返回。这样可以确保该类在整个应用中只有一个实例。
(1)什么是 JavaScript 的单例模式?它有什么作用?
JavaScript 的单例模式是一种只允许创建唯一一个实例对象的设计模式,可以确保整个系统中只有一个特定对象,并且提供了全局访问点以便于在整个系统中进行操作。单例模式主要用于管理资源、控制命名空间、全局配置和状态等。
(2)请实现一个 JavaScript 单例模式。
const Singleton = (function () {
let instance;
function createInstance() {
const object = new Object({name: 'Singleton Object'});
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
//如果 instance 不存在,则调用 createInstance() 方法创建一个新的实例对象,并将其赋值给 instance 变量。
//如果 instance 已经存在,则直接返回该对象。
}
return instance;
//可以确保只有一个实例对象被创建和使用
}
};
})();
// 调用示例
const singletonObj1 = Singleton.getInstance();
const singletonObj2 = Singleton.getInstance();
console.log(singletonObj1 === singletonObj2); // 输出结果为 true
(3)单例模式可以应用于哪些场景?
单例模式可以应用于需要全局访问和控制的场景,例如浏览器中的常用组件、数据库连接池、全局配置等。同时,单例模式还可以用于保护全局变量和方法,避免相互干扰和污染命名空间,提高代码的可维护性、可读性和可扩展性。
✍笔试题
// 请补全下面的 JavaScript 代码中的 add 方法
function add(n) {
/*TODO*/
}
/*补全 add 函数,输出对应结果 */
add(1)(2)();/*=>3 */
add(1)(2)(3)(4)(); /* =>10 */
add(1)(1)(1)(1)(1)(1)(1)(1)(1)(1)(); /* =>10 */
//答案:
function add(n){
if (!n) { return res } // 要是整个结果 6 10
res = n
return function (n) {
// 这一次传的参数 应该是 上面几次的和
return add(res + n)
}
}
console.1og(add(1)(2)(3)()); // 6
console.1og(add(1)(2) (3)(4)()); // 10
//定时器传参数 如下:
function fn(a) {
return function ({
console.1og(a)
}
}
setTimeout(fn(123),1000)
(5)私有变量
var getter,setter
(function(){
var privateA = 0
getter = function(){
return privateA
}
setter = function(){
if(typeof newVal !== 'number'){
throw new Error('不是number类型')
}
privateA = newVa1
}
})();
//privateA 变量和 getter 函数、setter 函数都被定义在同一个闭包内
//因此它们之间的作用域是相同的,并且可以互相访问彼此的变量和函数
利用闭包判断数据类型
function isType(type) [
return function (target) [
return '[object ${type}]'=== object.prototype.toString.ca11(target)
}
}
const isArray = isType('Array')
console.log(isArray([1,2,3]));// true
console.1og(isArray({}));
var data = [
{
name:'手机 / 运营商 / 数码',
child:[
{
name:'热门分类',
child:[
{name:'手机卡'},
{name:'宽带'},
{name:'5G套餐'}
]
},
{
name:'品牌墙',
child:[
{
name:'中国移动',
child:[
{name:'北京移动'},
{name:'广东移动'},
{name:'湖北移动'}
]
}
]
}
]
}
]
⭕padding 是控制元素内部与边框的距离,而margin是控制元素与周围元素的距离。