JavaScript这些基础可以不知道??(持续更新~~)

01. JS加载阻塞
02. 变量提升
03. TDZ(暂存性死区)
04. 块级作用域以及全局污染
05. 严格模式"use strict"
06. 类型检测
07. 隐式转换
08. 正式学习一下Symbol
09. 非常有趣的Set
10. 可以解决不能用对象作键的问题Map
11. 深究函数
12. 任务管理

精通JavaScript

1. JS加载阻塞

一般地,一个包含外部样式表和外部脚本问价的HTML载入和渲染过程是这样的:

  1. 浏览器下载HTML文件并开始解析DOM;
  2. 遇到样式表文件时,将其加入资源文件下载列表,继续解析DOM;
  3. 遇到脚本文件时,暂停DOM解析并立即下载脚本文件
  4. 脚本文件下载好后立即执行,此时脚本只能访问当前已加载的DOM元素;
  5. 脚本执行结束,继续解析DOM;
  6. DOM解析完成,出发DOMContentLoaded事件。

所以阻塞就是DOM还没有解析渲染完毕,由于JS文件的优先下载解析导致页面不能正常显示一段时间,也就是白屏

解决办法

  1. 推迟加载(延迟加载)

初始页面如果不依赖与js就可以用推迟加载,将js文件在body的最后引入。

  1. defer延迟加载

使用

  1. 异步加载

使用,它会异步加载JS不会导致页面阻塞,或者动态创建script标签

<script type="text/javascript">
	(function() {
    	let s = document.createElement('script');
    	s.type = 'text/javascript';
    	s.sync = true;
    	s.src = 'xxx.js';
    	let x = document.getElementByTarName('script')[0];
    	x.parentNode.insertBefore(s,x);
	})();
</script>

2. 变量提升

解析器会在执行代码之前,先解析一遍,在解析的过程中如果有变量的使用在变量的定义之前,那么解析器就会做一步操作,提升变量

1. var声明的变量提升

// 我们写的代码
console.log(a);		// undefined
var a = 1;
console.log(a);		// 1

// 真正执行的代码(解析器修改后的代码)
var a;
console.log(a);
a = 1;
console.log(a);

2. if(false)的变量提升

// 我们写的代码
var user = 'zs';
(function() {
    if(false) var user = 'ls';
    console.log(user);		// undefined
})();
// 解析器处理过后
var user = 'zs';
(function() {
    var user;
    if(false) user = 'ls';
    console.log(user);
})

3. TDZ(暂存性死区)

var定义的变量输出在定义之前会输出undefined,也就是变量提升;但是let/const声明的变量在声明前存在暂存性死区,会报错,也就是说,我们使用let/const定义变量必须先声明后使用

1. 声明变量时的TDZ

console.log(x);	// Cannot access 'x' before initialization
let x = 1;

2. 函数中的TDZ

(function work() {
	console.log(user);	// 报错
    let user = 'zs';
})();

3. 函数参数的TDZ

(function work(a=b, b=3) {})();	//Connot access 'b' before initialization

也就是说,在b还不知道是什么的情况下,是无法赋值给a

4. 块级作用域以及全局污染

let/const/var他们的共同点是全局作用域中声明的变量,在函数中都能使用

// var/let 都一样
let a = 3;
function fun() {
    a = 5;
    console.log(a); 	// 5;	
}
fun();
console.log(a);			// 5

个人理解是: 在局部使用到一个变量是会先在自己的局部范围内找,如果没有该变量,则再向上一级寻找

由于var没有块级作用域,它声明的变量不限制于自己的局部,造成的全局污染

块级作用域个人理解:在一对{}存在,但出了这里,啥也不是,没人知道,也没人认识

var i = 2;
for (var i=0;i<10;i++) {};
console.log(i);		// 10;

显然这不是我们想要的结果,我们不希望for循环里面的i影响到我们全局变量i;所以我们就使用有块级作用域的let来定义变量,就不会造成这样的污染

let i = 2;
for (let i=0;i<10;i++) {};
console.log(i);		// 2;

想一想我们所谓的全局不就是window对象嘛,它会不会也受到污染

// 先来看结果
var lsj = 'you';
console.log(window.lsj);		// you;

果然会污染,那当我们定义的变量刚好和window中的变量重名,那岂不是出事了

var screenLeft = 500;
console.log(window.screenLeft);		// 500

window.screenLeft自动获取窗口距离屏幕左边的距离,由于var的全局污染,导致它是一个定值了,所以我们应该避免使用var

5. 严格模式"use strict"

虽然我们了解到了let的好处和var的坏处,但是var依然是可以声明变量的,这时候使用严格模式就可以避免很多问题

1. 强制声明防止污染全局之不声明就报错

"use strict";
lsj = 'you';
console.log(lsj);		// lsj is not defined;

2. 关键词不允许作为变量,用就报错

"use strict";
var public = "xxx"; 	// Unexpected strict mode reserved word

3. 参数不允许使用重复参数名,用就报错

"user strict";
function xxx(name, name) {};	//Duplicate parameter name not allowed in this context

当我们给某个函数使用"use strict"时,这个要求会向下传递,也就是说,此函数内部所有内容都使用严格模式,而此函数之外不作要求

6. 类型检测

类型检测的方法挺多的,我们这之说三种typeof,instanceof,Object.prototype.toString(),首先我们应该清楚他们的使用格式 typeof,instanceof是一个操作符,并不是方法,Object.prototype.toString()是一个方法,所以用法有点区别

1. typeof检测基本数据类型

typeof xx;		// 使用方法;返回的是一个字符串类型的xx的数据类型
console.log(typeof '123');	// string; 注意返回的其实是"string";
// 所以这个问题我们就需要注意一下啦
console.log(typeof typeof 1); // string;凡是只要大于等于两次嵌套使用typeof的输出都是string;
代码结果
typeof 1;number
typeof 'str';string
typeof true;boolean
typeof NaN;number
typeof undefined;undefined
typeof null;object
typeof new Object();object
typeof new Array();object
typeof function() {};object

很明显typeof只能判断出基本数据类型(null除外)

2. instanceof:基于原型链的判断

原型和原型链也是JS的一个重点哦(不过先不慌)

[] instanceof Array;	// []基于Array类型? 返回值为 true
操作结果
let a = []; a instanceof Array;true
let a = {};a instanceof Object;true
let a =()=>{};a instanceof Function;true
let a = new Date(); a instanceof Date;true
let a = /w/g; a instanceof RegExp;ture

这就完了?就这?当然不会,还有一个烦人的事就是,以上的五种类型instanceof Object都是true,就问你怕不怕??这个要从原型说起了,

不着急,我们目前只要大概清楚这些的原型上都有Object就行了,好像也说得通,不愧是我

3. Object.prototypeo.toString()高级的方法

这里会涉及call(),apply()两个方法,它们两个除了传入的第二个参数有点区别没有其他区别

let a = 1;
Object.prototype.toString.call(a);	// [object Number];
a = {};
Object.prototype.toString.apply(a); // [object Object];
输入输出
let a = 1;[object Number]
let a = 'str';[object String]
let a = Symbol();[object Symbol]
let a = function(){};[object Function]

可以看出不管输入原始数据类型还是引用类型,Object.prototype.toString()都可以精准的返回它的类型

7. 隐式转换

基本上所有类型都可以转为Boolean类型

数据类型truefalse
String非空字符串''
Number非0数值0
Array数组不参与比较时候比较时的空数组
Object所有对象
undefinedundefined
nullnull
NaNNaN
console.log(3== true);	// false;  en?不是说好非空字符串转为true嘛?

往往在我们进行 == 比较的时候,两边的类型都要转变为Number类型,而在判断时需要转变为Boolean.

### 8. 正式学习一下`Symbol`

Symbol用于解决属性名冲突而产生的,Symbol值是唯一的,独一无二不会重复的

1. symbol独一无二

let zs = Symbol();
let ls = Symbol();
console.log(zs == ls);	// false;

2. Symbol不可以添加属性

let zs = Symbol();
zs.name = 'xxx';
console.log(zs.name);	// undefined;

3. 给Symbol添加一个描述

let zs = Symbol('我是张三');
let ls = Symbol('李四');
console.log(zs);		// Symbol(我是张三);
console.log(ls);		// Symbol(李四);

即使传入相同的描述它依旧是独一无二的,就跟现实生活中两个仅仅名字相同的人的关系一样

4. Symbol.for注册一个"名字"

let zs = Symbol.for('张三');
let ls = Symbol.for('张三');
console.log(zs == ls);	// true;感觉智商在被调戏,说好的独一无二呢?

Symbol.for('xx')xx这个描述注册了,这样以后用它描述的Symbol.for只能是我,想想其实和现实挺像的,但是不能在用名字来作比较了,身份证可以吧

5. Symbol.keyFor,询问注册描述(身份证号)

let zs = Symbol.for('张三');
console.log(Symbol.keyFor(zs));		// 张三;

let ls = Symbol('李四');
console.log(Symbol.keyFor(ls));		// undefined;

6. 给对象设置Symbol属性

Symbol出现的初衷就是解决属性名重复问题

  • Symbol声明和访问使用[]操作;
  • 不能使用 . 操作取值
let zsName = Symbol('张三');
let obj = {
    [zsName]: 'zhangsan'
};
console.log(obj[zsName]);		// zhangsan;

7. Symbol保护机制

for/in for/of都不能遍历对象中的 Symbol 属性,但是使用Object.getOwnPropertySymbols() 可以获取所有Symbol属性,使用 Reflect.ownKeys() 可以获取所有属性

let name = Symbol('xxx');
let obj = {
    [name]: 'you name', 	// 把名字保护起来
    age: 18
}
// for/in 遍历
for (let key in obj) {
    console.log(key);	// age; 不会输出name;
}
// Object.getOwnPropertySymbols()获取所有Symbol属性
for(const key of Object.getOwnPropertySymbols(obj)) {
	console.log(key);	// Symbol(xxx)
}
// Reflect.ownKeys()获取所有属性
for(const key of Reflect.ownKeys(obj)) {
    console.log(key);	// age,Symbol(xxx);
}

9. 非常有趣的Set

用于储存任何类型的唯一值

  • 只能保存值没有键名
  • 严格类型检测(1 和 '1’是不同的)
  • 值是唯一的
  • 遍历顺序是添加顺序,方便保存回调函数

1. 基本用法

let set = new Set();	// 初始化一个字典,可以传入初始数据(数组或字符串形式)
set.add(1);				// 添加一个内容
set.size;				// 获取字典内容数量
set.has(1);				// 返回true,判断是否存在检测值
set.delete(1);			// 删除一个内容
set.clear();			// 清空

2. 转换为数组

let set = new Set('12345');		// Set {'1','2','3','4','5'}
let set2Arr = [...set];			// ['1','2','3','4','5']

3. 遍历数据

使用keys()/values()/entried()都可以返回可迭代对象,因为Set只有value,所以返回的value,key是一样的

const test = new Set([1,2,3,4]);
console.log(test.values());		// [Set Iterator] { 1, 2, 3, 4 }
console.log(test.keys());		// [Set Iterator] { 1, 2, 3, 4 }
console.log(test.entries());	// [Set Iterator] { 1, 2, 3, 4 }	

也可以使用forEach for/of遍历

// forEach
let test = new Set([1,2,3,4]);
test.forEach((item,key)=>{console.log(item,key)});

// for/of
for (const item of test) {
    console.log(item);
}

4. 只能存放对象类型的WeakSet

WeakSet 结构同样不会存储重复的值,而且它的值只能是对象类型

  • 垃圾回收不考虑 WeakSet, 即被WakSet 引用是引用计数器不加一,所以对象不被引用是不管WeakSet是否在使用都将删除
  • 因为WeakSet是弱引用,由于其他地方操作成员可能不会存在,所以不可以进forEach遍历等操作
  • 因为是弱引用,WeakSet结构没有keys(),values(),entried()等方法和size属性
  • 因为是弱引用,所以当外部引用删除时,希望自动删除数据使用WeakMap
const test = new WeakSet();
const child = [1,1];
test.add(arr);		// 添加一个数据
test.delete(arr);	// 删除一个数据
test.has(arr);		// false,检索判断

5. WeakSet的垃圾回收

WeakSet保存的对象不会增加引用计数器,如果一个对象不被引用就会自动删除

  • 下例中的数组被引用,计数器+1
  • 数据有添加到了test的WeakSet中,引用计数还是1
  • 当数组设置为null是,引用计数-1此时对象引用为0
  • 当垃圾回收是对象被删除,这是WeakSet也就没有记录了
const test = new WeakSet();
let arr = ["ls"];		// 被引用,计数器+1
test.add(arr);			// 数据添加到了test的WeakSet中,引用计数还是1
arr = null;				// 将数组设置为null
console.log(test);		// WeakSet { [items unknown] }

setTimeout(()=>{
    console.log(test);	// WeakSet { [items unknown] }
})

10. 可以解决不能用对象作键的问题Map

Map 是一组键值对的结构,用于解决以往不能用对象作为键的问题

  • 具有极快的查找速度
  • 函数、对象、基本类型都可以作为键或值

1. 声明定义

可以接受一个数组作为参数,该数组的成员是一个表示键值对的数组

let m = new Map([
    ['zs','张三'],
    ['ls','李四']
]);
console.log(m.get('zs'));		// 张三

使用set方法添加元素,注意哦是set,之前的是add,支持链式操作

let map = new Map();
let obj = {
    name: '张三'
};
map.set(obj,'zs').set('name','ls');			// set的链式操作添加两个元素
console.log(map.entries())		// [Map Iterator] { [ { name: '张三' }, 'zs' ], [ 'name', 'ls' ] }

11. 深究函数

函数就是将复用的代码封装起来

1. 声明定义

在JS中函数也是对象函数是Function类创建的实例,但是标准语法是使用函数声明来定义函数

// 不常用的Function实例创建函数
let fun = new Function("title","console.log(title)");
fun('zs');	// zs

// 标准语法
function func(title) {
    console.log(title);
};
func('ls');	// ls

在对象中,如果我们要书写一个函数属性的话,可以使用它的简写形式

let user = {
    name: 'zs',
    getName: function (name) {
        return this.name;
    },
    // 简写
    setName(value) {
        this.name = value;
    }
}
user.setName('ls');
console.log(user.getName());	// ls

我们定义的所有函数都是压入window对象中,也就是说全局的外层就是window对象,window中本来存在一些自己的函数,如果我们写的函数名刚好和window对象中的相同,那么原本window中的方法就会无法被正常调用,但是用let/const声明的函数不会压入window对象中

function test() {
    console.log('hhh');
};
window.test();	// hhh

let letTest = function() {
    console.log('let声明');
}
window.letTest();	// window.letTest is not a function

2. 匿名函数

匿名函数就是指函数本身没有名字,只是将这个函数赋值给了一个变量

let test = function(num) {
    return ++num;
};
console.log(hd instanceof Object);	// true

let newTest = test;
console.log(newTest(3));	// 4

小知识点:如果let newTest = test();的话,结果就不一样了哦,不带括号相当于将整个函数赋值给新变量,如果加括号的话就相当于把函数的返回值赋值给新变量

标准声明的优先级高于赋值声明

console.log(test(3));		// 4
// 标准声明
function test(num) {
	return ++num;
}
// 赋值声明
var test = function(num) {
    return --num;
};

而且有同名的标准声明函数之后,不能够在使用let/const赋值式声明,只能用var

3. 立即执行函数(函数被定义时立即执行)

  • 可以用来定义私有作用域防止污染全局作用域(前面说过了全局污染哦)
"use strict";
(function () {
    var name = 'zs';
})();
console.log(web); //web is not defined
  • let/const有块级作用域,所以只需要一对{}就可以将变量放在私有作用域
{
    let name = 'ls';
}
console.log(name);	// name is not defined

4. 函数提升

函数提升 ~= 变量提升(还记得吗??),而且函数提升优先级大于变量提升,但是赋值函数不存在函数提升

// 变量提升
console.log(name);	// undefined
var name = 'zs';

// 函数提升
console.log(test(2));	// 3
function test(num) {
    return ++num;
}
// 赋值函数不会提升
var test = function (num) {
    console.log(--num);
};

实参,形参,默认参数,可选参数那些都挺好理解的,尤其是ES6给出的方法,真贴心

5. arguments是什么?

arguments是函数获得的所有参数的集合,配合ES6的展开语法

function sum(...args) {
    return args.reduce((a,b) => {
        return a + b;
    })
}
console.log(sum(1,2,3,4)); 	// 10
// 小知识点,箭头函数在只有一个参数时()可以省略,只有一条return时大括号可以省略,所以第二三行可以改写
return args.reduce((a,b)=>a+b);

12. JS任务管理

JavaScript是单线程,也就是说同一个时间只能处理一个任务,为了协调时间、用户交互、脚本、UI渲染和网络等行为,防止主线程阻塞,Event Loop的方案就诞生了

JavaScript 处理任务是在等待、执行、休眠等待中不断循环(Event Loop)

  • 主线程任务全部完成,才开始执行队列任务中的任务
  • 有新的任务就加入任务队列,采用先进先执行策略

任务包括script(整体代码) setTimeout setInterval DOM渲染 DOM事件 Promise XMLHTTPRequest等

理解一下宏任务和微任务

宏任务包括同步宏任务和异步宏任务,没必要太死磕概念

console.log('同步宏任务,代号001');
setTimeout(function() { console.log("异步宏任务")}, 0);
new Promise(resolve =>{ console.log("Promise是同步宏任务,代号002")})		// 注意了Promise本体是一个同步宏任务
.then(function() {
    console.log(".then是微观任务01");
    resolve();
})
.then(function() {
    console.log(".then是微观任务02");
});
console.log("同步宏任务,代号003");
  1. 单线程,先走同步宏任务,见到异步任务先放入事件队列(.then是异步微任务)
  2. 同步任务执行完毕,再去遍历事件队列的微任务
  3. 微任务做完后开始顺序执行异步任务

总结一下也就是:同步 -> 微任务 -> 异步任务

// 输出
同步宏任务,代号001
Promise是同步宏任务,代号002
同步宏任务,代号003
.then是微观任务01
.then是微观任务02
异步宏任务

输出问题

以下代码输出结果

setTimeout (() => {
    console.log("定时器");
    setTimeout(() => {
        console.log("定时器2");
    },0);
    new Promise(resolve => {
        console.log("Promise in timeout");
        resolve();
    }).then(() => {
        console.log("then in timeout");
    });
},0);
new Promise(resolve => {
    console.log("Promise");
    resolve();
}).then(() => {
    console.log('then');
});
console.log('333');
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值