值类型和引用类型
- 值类型:指基本类型,即
number,string,boolean,undefined,null,symbol。它们总是通过值复制的方式赋值和传递值。 - 引用类型:
Object,Array,Date,Regexp,Function。它们总是以引用复制的方式复制和传递值。
手写获取引用类型的函数
方法:利用 Object.prototype.toString.call() 可以获取内部资源的特性,可以变向获取数据的类型。
function getType(data) {
let res = Object.prototype.toString.call(data); //获取内部属性
let index = res.indexOf(' '); //找到第一个空格的下标
return res.slice(index + 1, -1) //截取
}
手写深度拷贝
方法:使用 WeapMap 处理循环引用
function deepClone(obj, cloneObjects = new WeakMap()) {
//当obj为null或者不是数组或对象的处理
if (obj === null || typeof obj !== 'object') {
return obj;
}
//如果已经克隆过该对象,直接返回克隆的对象,避免循环引用
if (cloneObjects.has(obj)) {
return cloneObjects.get(obj);
}
//根据原对象创建数组或对象
let clone = Array.isArray(obj) ? [] : {};
//将克隆的对象加入obj的映射中
cloneObjects.set(obj, clone);
// 递归克隆原对象的每个属性
for (let key in obj) {
//不克隆原对象的原型属性
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], cloneObjects);
}
}
return clone;
}
//例子
let obj = { name: '小明', value: { a: 1, b: 2, arr: [0, 1, 2, 3] } };
let clone = deepClone(obj);
clone.name = '小红';
clone.value.b = 10;
clone.value.arr[1] = 10;
console.log(obj)
console.log(clone);
效果:
JavaScript的作用域和作用域链
作用域 是指在 JavaScript 中变量的可访问性和可见性。也就是说在程序中哪些部分可以访问这个变量,或者变量在哪些部分中是可见的。
作用域的类型
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域
任何不在函数或者大括号里声明的变量,都是在全局作用域下的,全局作用域下声明的变量可以在程序中任何位置访问到。例如:
let a = 'abc'; // 全局变量
function fuc() {
console.log(a);
}
fuc(); // 打印 'abc'
函数作用域
函数作用域也叫局部作用域,在函数内部声明的变量都是在函数作用域下的,函数作用域下声明的变量只能在函数内部中访问,函数以外不能访问。例如:
function fuc() {
let a = 'abc'; // 函数作用域
console.log(a);
}
fuc(); // 打印 'abc'
console.log(a); // 报错 Uncaught ReferenceError: a is not defined
块级作用域
在 ES6 中引入了 let 和 const,与 var 不同,用 let 和 const 声明的变量在大括号中的 是存在块级作用域中的,在大括号外部不能访问。例子:
{
//块级作用域
let a = 'abc';
const b = 'bcd';
var c = 'cde'
console.log(a); // 打印 'abc'
console.log(b); // 打印 'bcd'
console.log(c); // 打印 'cde'
}
console.log(c); // 打印 'cde'
console.log(a); // 报错 Uncaught ReferenceError: a is not defined
上面的代码可以看出,var 在大括号外也可以访问。使用 var 声明的变量不存在块级作用域中。
作用域链
在 js 中要使用到一个变量时,会在当前作用域下寻找该变量,如果没找到,会一层一层的往上找,直到全局变量还是没找到就宣布放弃,这就是作用域链。
var a = 'abc';
function f1() {
var b = 'bcd';
function f2() {
var c = 'cde';
console.log(a); // 自由变量,顺着作用域链向上找到变量 a 打印 'abc'
console.log(b); // 自由变量,顺着作用域链向上找到变量 b 打印 'bcd'
console.log(c); // 本作用域的变量 打印 'cde'
}
f2();
}
f1();
闭包
概念
简单来说,就是能够访问另外一个函数作用域中的变量的函数,例如:
function fn() {
let a = 6
return function () {
console.log(a)
}
}
用途
- 避免全局变量的污染
- 储存变量
- 封装私有变量
特性
- 函数嵌套函数
- 函数内部可以引入外部函数的参数和变量
- 参数和变量不会被 JS 的垃圾回收机制回收
缺点
闭包在会在内存中常驻,容易导致内存增大,如果使用不当,会发生内存泄漏。最好在使用完这个函数或者变量在函数内部用完时,进行销毁。
this 的值
- this 永远指向一个对象
- this 的指向完全取决于函数调用的位置
例子:
function fn() {
console.log(this);
}
var obj = {
name : 'jason',
f: fn
}
obj.f(); // 运行环境是在 obj 中,this 指向 obj 对象
fn(); // 运行环境是在全局作用域中,this 指向全局作用域对象 window
this 常用的 5 个场景
- 事件绑定
- 构造函数
- 定时器
- 函数对象的 call() 方法
- 函数对象的 apply() 方法
事件绑定中的 this
三种情况:行内绑定、动态绑定、事件监听
行内绑定:
<!-- 此函数的运行环境在结点对象中,因此this指向当前结点对象 -->
<input type="button" value="按钮1" onclick="console.log(this)">
<input type="button" value="按钮2" onclick="clickFun()">
function clickFun() {
console.log(this) // 此函数的运行环境在全局window对象下,因此this指向window;
}
动态绑定和事件监听:
<input type="button" value="按钮" id="btn">
var btn = document.getElementById('btn');
btn.onclick = function () {
console.log(this); // 该函数执行环境在该节点对象中, this 指向该结点对象
}
构造函数的 this
function Pro(){
this.x = '1';
this.y = function(){};
}
var p = new Pro();
定时器中的 this
定时器 setInterval 和 setTimeout 中的 this 是指向全局作用域对象 window ,但有时候需要修改this 的指向,常用的方法:
外部保存 this 值
var name = 'window';
var obj = {
name: 'obj',
fn: function () {
var that = this;
var timer = null;
clearInterval(timer);
timer = setInterval(function () {
console.log(that.name); //obj
}, 1000)
}
}
使用箭头函数
箭头函数值没有自己的 this,this 默认继承外部的作用域
var name = 'window';
var obj = { name: 'obj',
fn: function () {
var timer = null;
clearInterval(timer);
timer = setInterval( () => {
console.log(this.name); //obj
}, 1000)
}
}
函数对象的 call() apply() 方法
函数作为对象提供了 call() apply() 方法,他们可以调用函数,用于指定本次调用函数时 this 的指向
call() 方法
call方法使用的语法规则
函数名称.call(obj,arg1,arg2...argN);
参数说明:
obj:函数内this要指向的对象,
arg1,arg2...argN :参数列表,参数与参数之间使用一个逗号隔开
var lisi = { names: 'lisi' };
var zs = { names: 'zhangsan' };
function f(age) {
console.log(this.names);
console.log(age);
}
f(23);//undefined
//将f函数中的this指向固定到对象zs上;
f.call(zs, 32);//zhangsan
f.call(lisi, 32);//lisi
apply() 方法
函数名称.apply(obj,[arg1,arg2...,argN])
参数说明:
obj :this要指向的对象
[arg1,arg2...argN] : 参数列表,要求格式为数组
var lisi = { names: 'lisi' };
var zs = { names: 'zhangsan' };
function f(age, sex) {
console.log(this.names);
console.log(age + sex);
}
//将f函数中的this指向固定到对象zs上;
f.apply(zs, [32, '男']);//zhangsan
f.apply(lisi, [32, '男']);//lisi
Event loop 事件循环、微任务、宏任务
Js 是单线程,执行顺序:先同后异,先微后宏
同步任务
即主线程上的任务,由上到下顺序依次执行,当前任务执行完毕后,才执行下一个任务。
异步任务
不进入主线程,而是进入任务队列的任务,执行完毕后会产生一个回调函数,并通知主线程。在主线程上的任务执行完毕后,就会调取最早通知的回调函数,使其进入主程序中执行。
异步任务又分为微任务和宏任务
微任务:Promise.then()、async/await
宏任务:定时器、ajax、事件绑定
Event loop 事件循环机制
- 进入Script标签,第一次循环
- 遇到同步代码,立即执行
- 遇到宏任务,加入到宏任务队列
- 遇到微任务,加入到微任务队列
- 执行同步代码
- 执行微任务,清空队列
- 执行宏任务,清空队列
Promise、async/await、setTimeout/setInterval 执行顺序
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
// 同步
// script start
// async1 start
// async2
// promise1
// script end
// 异步
// 微任务
// asnyc1 end
// promise2
// 宏任务
// setTimeOut