JS高级
作用域
词法作用域/静态作用域
定义在词法阶段的作用域。 就是你写代码的时候,将变量写在哪里决定的,因此,当词法分析器处理代码时,会保持作用域不变 (eval,with除外)
全局作用域
script标签和js文件的【最外层】就是全局作用域,在此声明的变量在函数内部也可被访问。全局作用域中声明的变量,任何其他作用域都可以被访问
局部作用域
- 函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法访问 - 块作用域
在js中用{}包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法被访问
let声明的变量会产生块作用域,var 不产生块作用域
const声明的常量也不会产生块作用域
不同代码块直接的变量无法互相访问
推荐使用const或let
作用域链
作用域链的本质上的底层的变量查找机制
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
上下文
词法作用域,是在写代码的时候,或者在定义时,确定的; 而动态作用域,是在运行时确定的
foo(10);
function foo(num) {
//num=10
var foo; //=undefined
console.log(foo); //undefined
foo = num;
//=10
console.log(foo);
}
console.log(foo); //function
foo = 1;
console.log(foo); //1
闭包
函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局作用域下可访问,就形成了闭包
function outer() {
let i = 1;
function fn() {
console.log(i);
}
return fn;
}
const fun = outer();
fun();
闭包的使用场景
当一个函数的执行,和上下文相关的时候,基本上就有闭包了
例如:防抖和节流
- 防抖
单位时间内,频繁触发事件,只执行最后一次
使用场景:搜索框搜索输入,只需要用户最后一次输入完,再发送请求 手机号、邮箱验证输入检测
核心是利用setTimeout定时器实现
- 声明定时器变量
- 事件触发的时候先判断是否有定时器,如果有闲清除以前的定时器
- 如果没有定时器,则开启定时器,存入到定时器变量里面
- 定时器里面写函数调用
function debounce(fn, t) {
let timer;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn();
}, t);
};
}
- 节流
单位时间内,频繁触发事件,只执行一次
简单理解:在500ms内,不管触发多少次事件,只执行一次
使用场景:页面尺寸缩放resize、滚动条滚动scroll
节流的核心就是利用定时器(setTimeout)来实现
- 声明一个定时器变量
- 当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器
- 如果没有定时器,则开启定时器,记得保存到变量里面
3.1 定时器里面调用执行的函数
3.2 定时器里面要把定时器清空
function throttle(fn, t) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(function () {
fn();
timer = null;
}, t);
}
};
}
this
this指向是什么?
- this指向是根据上下文动态决定的
- 在简单调用时,this默认指向的是window/global/undefined (浏览器/node/严格模式)
- 对象调用时,绑定在对象上
- 使用call,apply,bind时,绑定在指定的参数上
- 使用new 关键字时,绑定到新创建的对象上
(以上三条优先级:new > apply/call/bind >对象调用) - 使用箭头函数,根据外层的规则决定
沿用上一级的this,过程:向外层作用域中,一层一层查找this,直到有this的定义
不适用:构造函数、原型函数、字面量对象中的函数
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
},
};
var fn1 = foo.fn;
fn1(); //window undefined 简单调用
foo.fn(); //{bar: 10, fn: ƒ} 10 对象调用
const student = {
name: 'lucas',
fn: function () {
return this;
},
};
console.log(student.fn() === student); //true
const person = {
name: 'luyi',
brother: {
name: 'yunyin',
fn: function () {
return this.name;
},
},
};
console.log(person.brother.fn()); //yunyin
const o1 = {
text: 'o1',
fn: function () {
return this.text;
},
};
const o2 = {
text: 'o2',
fn: function () {
return o1.fn(); //this指向o1
},
};
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn;
return fn(); //this指向window
},
};
console.log(o1.fn()); //o1
console.log(o2.fn()); //o1
console.log(o3.fn()); //undefined
call、bind、apply
-
call
调用函数,同时指定被调用函数中this的值
语法:fun.call(thisArg,arg1,arg2)const obj = { uname: 'aaa', }; function fn(x, y) { console.log(this); console.log(x + y); } fn.call(obj, 1, 2); -
apply
调用函数,同时指定被调用函数中this的值
语法:fun.apply(thisArg,[argsArray])
const obj = {
age: 18,
};
function fn(x, y) {
console.log(this);
console.log(x + y);
}
fn.apply(obj, [1, 2]);
-
bind
不会调用函数,能改变this指向,返回值是个函数,但是这个函数里面的this是更改过的objconst obj = { age: 18, }; function fn() { console.log(this); } const fun = fn.bind(obj); fun();
总结
- 相同点:
都可以改变函数内部的this指向 - 区别点:
call和apply会调用函数,并且改变函数内部this指向
call和apply传递的参数不一样,call传递参数arg1,arg2...,apply必须为数组形式[arg]
bind不会调用函数,可以改变this指向 - 主要应用场景:
call调用函数并且可以传递参数
apply经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向,比如改变定时器内部this指向