【自用】JS知识点全解手册
js学过很久了,因为框架的出现以至于对原生js的熟悉程度指数级下降。但是不可否认js在前端领域的重要性。借着最近的空闲时间,能有机会从零开始梳理一遍js的所有常用语法及知识点。于是有了这篇文章,简单记录一下,仅作自用。
一、js基础语法
1.变量命名
1 字母 下划线 $
2 不能是关键字
3 不能以数字开头 1a
4 严格区分大小写
5 有意义
6 驼峰式命名
2.js数据类型
分类
1 基本数据类型(number,boolean,string,undefined,null,symbol,bigint) 7种
Symbol
: ES6
引入的一种新的原始值,表示独一无二的值,主要为了解决属性名冲突问题。Symbl 确保唯一,即使采用相同的名称,也会产生不同的值,
Bigint
:ES2020
新增加,是比 Number
类型的整数范围更大。
2 引用数据类型 object(包括普通Object、Function、Array、Date、RegExp、Math) 1种
变量声明 但是未赋值其类型就是undefined ,变量声明 但是未赋值 其默认值是undefined
区别
赋值:基本数据类型的值是不可变的,任何方法都无法改变一个基本类型的值,当这个变量 重新赋值后看起来变量的值是改变了,但是这里变量名只是指向变量的一个指针,所以改变的是指针的指向改变,该变量是不变的,但是引用类型可以改变
基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,引用数据类型的赋值是对象引用
添加:基本数据类型不可以添加属性和方法,但是引用类型可以
比较:基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同
存放:基本数据类型是存放在栈区的,引用数据类型同时保存在栈区和堆区
📌JS 判断类型:
判断方法:typeof(),instanceof,Object.prototype.toString.call()等
typeof()
- typeof()可以返回7种数据类型:
number、string、boolean、undefined、object、function
,以及ES6
新增的symbol
typeof
不能正确区分数据类型。对于原始类型,除null
都可以正确判断;对于引用类型,除function
外,都会返回"object"
typeof
返回值为string
格式typeof(NaN) -> "number"
typeof
未定义的变量不会报错,返回"undefiend"
typeof(null) -> "object"
: 遗留已久的bug
instanceof
instanceof
判断对象的原型链上是否存在构造函数的原型。只能判断引用类型。instanceof
常用来判断A
是否为B
的实例
// A是B的实例,返回true,否则返回false
// 判断A的原型链上是否有B的原型
A instaceof B
Object.prototype.toString.call()可以精准判断数据类型
ES6
提供的新方法Array.isArray()
区分数组与对象。如果不存在Array.isArray()
呢?可以借助Object.prototype.toString.call()
进行判断。- 会返回一个形如
"[object XXX]"
的字符串
在 IE6 中,null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!
封装一个类型判断的函数
var class2type = {};
// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function (item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})
console.log(class2type);
function type(obj) {
// 一箭双雕
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
3.数据类型转换
toString(): 返回当前对象的字符串形式;valueOf()
: 返回该对象的原始值
number->string: ①a=a.toString() ②a=String(a) ③a = a + '';
string->number:①Number() ②parseFloat() ③parseInt() ④ / 隐式转换
var a = '123.45z';
a = parseInt(a); // a = 123 遇到第一个非数字字符为止,假如第一个就是非数字直接返回NaN
console.log(typeof NaN); // NaN (not a number) 类型还是number
console.log(NaN === NaN); // false
a = parseFloat(a); //转成小数
a = Number(a); // 只要字符串里有除小数点以外的字符就返回NaN
var b = '10';
b = b / 1; // 隐式转换 '10'-->10 10-0
toString()
: 返回当前对象的字符串形式;valueOf()
: 返回该对象的原始值- 各个类型下两个方法返回值情况对比
类型 | valueOf | toString |
---|---|---|
Array[1,2,3] | 数组本身[1, 2, 3] | 1,2,3 |
Object | 对象本身 | [object Object] |
Boolean类型 | Boolean值 | "true"或"false" |
Function | 函数本身 | function fnName() |
Number | 数值 | 数值的字符换表示 |
Date | 毫米格式时间戳 | GMT格式时间字符串 |
一元强制转换
+undefined // NaN
+null // 0
+true // 1
+false // 0
+'111' // 111
+'0x100F' // 4111
+'' // 0
'b' + 'a' + + 'a' + 'a' // 'baNaNa'
+Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
4.运算符
算术运算符
比较运算符
==不严格相等 只需要内容相同即可 类型可以不一样
=== 严格相等 内容和类型都需要一样
Object.is()
来进行相等判断时,一般情况下与 ===
相同,它处理了一些特殊的情况,比如 +0
和 -0
不再相等,两个 NaN
是相等的
赋值运算符
逻辑运算符&& || !
表达式1 && 表达式2 当表达式1的值是真,此时直接返回表达式2的值 表达式1的值是假(false,0,'',null,NaN,undefined),直接返回表达式1的值,表达式2不执行(短路现象)
表达式1 || 表达式2 当表达式1的值是真,此时直接返回表达式1的值 表达式2不执行(短路现象) 表达式1的值是假(false,0,'',null,NaN,undefined),直接返回表达式2的值
注意:逻辑运算的结果不一定就是true或者false这两个Boolean值,而是要看具体表达式的情况
三目运算符
表达式1?表达式2:表达式3
运算符优先级
() (2+3)*7
++ -- ! a+++5 (a++)+5
\* / % --->+ -
\> >= < <=
== === != !==
&&-----> ||
=
,
var num = (1, 2 + 5, 3);
console.log(num);//3
5.流程控制
if
switch
switch (num) {
default: console.log('error');
break;
case 0:console.log('星期日');
break;
}
循环结构(重复结构)
for/while/do-while
在检查while()条件是否为真之前,该循环首先会执行一次do{}之内的语句,然后在while()内检查条件是否为真,如果条件为真的话,就会重复do...while这个循环,直至while()为假
do-while循环体至少执行一次 while循环的循环体可能一次也不执行
6.数组
判断数组
Array.prototype.isPrototypeOf(arr)
**arr instanceof Array ** 如果网页中包含多个框架, 在这种情况下会不准确
Array.isArray(arr)
Object.prototype.toString.call(arr)==='[object Array]'
arr.constructor === Array 因为constructor
可以被重写,所以不能确保一定是数组。
数组的属性
length获取该数组元素个数
数组的方法
push(数组后面追加) /unshift(在数组前面追加)
-
push在原数组的末尾添加一个或多个元素
-
返回值 返回添加元素之后的数组长度
pop删除数组的最后一个元素/shift删除数组第一个元素 不需要传参数 返回被删除的那个元素
splice(参数1[参数2,参数3,...])
参数1 是必须的 表示从什么下标开始
参数2 ①没有的情况下 表示删除到数组末尾 ②假如是0 表示此次操作是添加元素,参数3以及
之后的都是添加的元素 ③假如是大于0的数 表示删除几个元素 用几个值替换(修改)
var arr2 = [8, 10, 9];
var res=arr2.splice(1); // 从下标为1开始 删除后面全部元素 删掉10 9 res=[10,9]一般不关心返回值
arr2.splice(2, 0, 20, 30); // 添加元素 参数至少三个
arr2.splice(1, 2, 20, 30); // 替换
如果两个参数 从下标为(参数1)开始,删掉(参数二)个元素
如果只有一个参数,未指定删除元素个数,则把参数一后面的元素全部删除
如果参数二为0 表示此次操作是添加元素,参数3以及之后的都是添加的元素
如果参数二大于0 且存在参数3、4...... 那么表示先删掉什么元素,再用什么元素替换
slice(start,end) 截取
- 从start位置开始截取到end为止 不包含end位置的元素 原数组不变
indexOf() 在数组中检索元素 / lastIndexOf()
- 参数 需要检索的值
- 假如从左到右找到则返回第一个满足条件的下标 假如找不到返回-1
function noRepeat(arr) {
var newArr = [];
// 每次对原数组元素进行赋值之前判断是否在新数组中
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr[newArr.length] = arr[i];
}
}
return newArr;
}
var newArr = noRepeat(arr);
forEach((item,index)=>{}) 高阶函数——函数的参数是一个函数或者返回值是一个函数 break
无法在 forEach
中使用。return无法中断
forEach。try catch
可以中断 forEach
的,而且是唯一可以中断 forEach
的方式
item 代表数组每个元素 index代表数组元素的索引
some判断是否有满足条件的元素 返回值 布尔值,找到满足条件的第一个元素 则终止循环 返回true,找不到则返回false
filter 过滤 把满足条件的所有元素筛选出来 新的数组 由满足条件的原数组元素组成
map 对元素进行遍历(迭代)处理 返回值 根据原数组产生长度一样的新的数组 新数组的每一个元素由原数组对应的元素带入函数参数后的返回值
reduce 参数
- 参数1 表示累计值默认会取数组第一个元素值 下次循环的时候会取上一次函数的返回值
- 参数2表示当前的值 第一次会取数组第二个值
- 参数3表示当前的值对应的索引
- 参数4表示原数组 返回值 最后一个元素带入函数之后的返回值
var sum = arr.reduce(function (start, v, i, arr) {
console.log(start);
// console.log(v);
// console.log(i);
// console.log(arr);
return start + v;
}, 0);
concat 连接数组 返回值是合并后的新数组
let arr=[1,2,[5,6,[7]]]
console.log(...arr); // 1 2 [ 5, 6, [ 7 ] ]
console.log([].concat(arr)) // [ 1, 2, [ 5, 6, [ 7 ] ] ]
console.log([].concat(...arr)) //[ 1, 2, 5, 6, [ 7 ] ]
join 把数组元素以某种分隔符连接到一起 形成一个字符串
// [1,2,3] ===> 1,2,3 1:2:3
var str = arr.join('😂; // 默认以逗号连接
reverse 对数组进行逆序 会改变原数组 [1,2,3]==>[3,2,1]
sort 对数组元素排序 改变原数组
var arr3 = [150, 15, 4, 8, 3];
arr3.sort(function(a, b) {
//return a - b; // 升序
return b - a; // 降序
});
📌数组的高阶函数
总结:
(一)哪些数组方法会修改原数组?哪些不会?
- 改变自身值的方法
pop push shift unshift splice sort reverse
// ES6新增
copyWithin
fill
- 不改变自身值的方法
join // 返回字符串
forEach // 无返回值
concat map filter slice // 返回新数组
every some // 返回布尔值
reduce // 不一定返回什么
indexOf lastIndexOf // 返回数值(索引值)
// ES6新增
find findIndex
(二)new Array()
与 Array.of()
的区别是什么?
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
new Array(n) ,只有一个参数时,构造的并非只含 n 的数组,而是 1 * n 的数组,值全为 undefined
(三)includes 与 indexOf的区别是什么?
indexOf
: 返回元素在array
中的位置,不存在就返回-1
includes
: 用来判断元素是否存在 可以检测NaN
includes
返回值为true/false
,在判断数组中是否存在某元素时,includes
的可读性更好
(四)foreach跳出循环
除了抛出异常以外,没有办法中止或跳出 forEach()
循环。
可以使用 some
和 every
来替代 forEach
函数: every
在碰到 return false
的时候,中止循环。some
在碰到 return true
的时候,中止循环。
(五)数组去重
(1)使用双重 for 和 splice
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
//第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
// 删除后注意回调j
j--;
}
}
}
return arr;
}
(2)使用 indexOf
或 includes
加新数组
//使用indexof
function unique(arr) {
var uniqueArr = []; // 新数组
for (let i = 0; i < arr.length; i++) {
if (uniqueArr.indexOf(arr[i]) === -1) {
//indexof返回-1表示在新数组中不存在该元素
uniqueArr.push(arr[i])//是新数组里没有的元素就push入
}
}
return uniqueArr;
}
// 使用includes
function unique(arr) {
var uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
//includes 检测数组是否有某个值
if (!uniqueArr.includes(arr[i])) {
uniqueArr.push(arr[i])//
}
}
return uniqueArr;
}
❓(3)sort 排序后,使用快慢指针的思想
function unique(arr) {
arr.sort((a, b) => a - b);
var slow = 1,
fast = 1;
while (fast < arr.length) {
if (arr[fast] != arr[fast - 1]) {
arr[slow ++] = arr[fast];
}
++ fast;
}
arr.length = slow;
return arr;
}
(4)ES6
提供的 Set
去重
function unique(arr) {
const result = new Set(arr);
return [...result];
//使用扩展运算符将Set数据结构转为数组
}
(5)使用哈希表存储元素是否出现(ES6
提供的 map
)
function unique(arr) {
let map = new Map();
let uniqueArr = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 如果有该key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果没有该key值
uniqueArr.push(arr[i]);
}
}
return uniqueArr ;
}
map
的键可以是任意类型,对象的键只能是字符串类型。
(6)filter
配合 indexOf
function unique(arr) {
return arr.filter(function (item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
//不是那么就证明是重复项,就舍弃
return arr.indexOf(item) === index;
})
}
(7)reduce
配合 includes
function unique(arr){
let uniqueArr = arr.reduce((acc,cur)=>{
if(!acc.includes(cur)){
acc.push(cur);
}
return acc;
},[]) // []作为回调函数的第一个参数的初始值
return uniqueArr
}
(六)数组扁平化(拍平)flat的实现
Array.prototype.flat() 特性总结
- 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
- 不传参数时,默认“拉平”一层,可以传入一个整数,表示想要“拉平”的层数。
- 传入
<=0
的整数将返回原数组,不“拉平”。Infinity
关键字作为参数时,无论多少层嵌套,都会转为一维数组 - 如果原数组有空位,
Array.prototype.flat()
会跳过空位。
如何实现
思路:在数组中找到是数组类型的元素,然后将他们展开
遍历数组:
// for...of
for (let value of arr) {
console.log(value);
}
// for...in
for (let i in arr) {
console.log(arr[i]);
}
// forEach 循环
arr.forEach(value => {
console.log(value);
});
// entries()
for (let [index, value] of arr.entries()) {
console.log(value);
}
// keys()
for (let index of arr.keys()) {
console.log(arr[index]);
}
// values()
for (let value of arr.values()) {
console.log(value);
}
// reduce()
arr.reduce((pre, cur) => {
console.log(cur);
}, []);
// map()
arr.map(value => console.log(value));
实现代码:
// 方法 1
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}
console.log(flatten(arr))
//reduce
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr))
// 栈思想
function flat(arr) {
const result = [];
const stack = [].concat(arr); // 将数组元素拷贝至栈,直接赋值会改变原数组
//如果栈不为空,则循环遍历
while (stack.length !== 0) {
const val = stack.pop();
if (Array.isArray(val)) {
stack.push(...val); //如果是数组再次入栈,并且展开了一层
} else {
result.unshift(val); //如果不是数组就将其取出来放入结果数组中
}
}
return result;
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
flat(arr)
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];
//通过传入整数参数控制“拉平”层数
function flat(arr, num = 1) {
return num > 0
? arr.reduce(
(pre, cur) =>
pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),
[]
)
: arr.slice();
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
flat(arr, Infinity);
function* flat(arr, num) {
if (num === undefined) num = 1;
for (const item of arr) {
if (Array.isArray(item) && num > 0) { // num > 0
yield* flat(item, num - 1);
} else {
yield item;
}
}
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
// 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
// 也就是遍历器对象(Iterator Object)。所以我们要用一次扩展运算符得到结果
[...flat(arr, Infinity)]
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];
Array.prototype.fakeFlat = function(num = 1) {
if (!Number(num) || Number(num) < 0) {
return this;
}
let arr = this.concat(); // 获得调用 fakeFlat 函数的数组
while (num > 0) {
if (arr.some(x => Array.isArray(x))) {
arr = [].concat.apply([], arr); // 数组中还有数组元素的话并且 num > 0,继续展开一层数组
} else {
break; // 数组中没有数组元素并且不管 num 是否依旧大于 0,停止循环。
}
num--;
}
return arr;
};
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
arr.fakeFlat(Infinity)
(七)类数组转化为数组
Array.prototype.slice.call()
const arrayLike = {
0: '111',
1: '222',
length: 2
}
console.log(Array.prototype.slice.call(arrayLike))
Array.from()
const arrayLike = {
0: '1',
1: '2',
length: 2
}
console.log(Array.from(arrayLike)) // ["1", "2"]
(...)展开运算符 扩展运算符调用的是遍历器接口,如果一个对象没有部署此接口就无法完成转换
如果部署了遍历器接口,例如 arguments
类数组,便可以使用扩展运算符。
function fn() {
console.log([...arguments])
}
fn(1,2,3) // [1, 2, 3]
7.字符串
三种创建字符串的方式
var str = '1';
var str1 = String('1');
var str2 = new String('1');
console.log(str === str1) // true
console.log(str1 === str2) // false
console.log(str === str2) // false
console.log(typeof(str)) // string
console.log(typeof(str1)) // string
console.log(typeof(str2)) // object
JavaScript
为了便于基本类型操作,提供了三个特殊的引用类型(包装类),即 Number,String,Boolean
,它们的 [[PrimitiveValue]]
属性存储它们的本身值。基本类型的方法与属性是"借"来的,去向对应包装类"借"来的。
修改 str.length
是无法做到修改字符串的长度的。str
为原始值,调用 length
相当借用 new String(str).length
,修改的是 new String(str)
的 length
,跟原始值 str
无关
new String()
生成的字符串是对象类型,生成的字符串的 length
属性不止是不可写,而且是不可枚举、不可配置的。
📌8.JSON
JSON.stringify
可以将数组或者对象转化成 JSON
字符串。JSON.parse
可以将 JSON
字符串转化为数组或对象。JSON.stringfy
localStorage
只能存取字符串格式的内容,因此存之前转换成JSON
字符串,取出来用时,再转化成数组或对象。
9.函数
一个函数没有执行return 默认返回值是undefined
实参和形参按一一对应原则进行传递 多余的形参默认undefined,多余的实参没意义
confirm()显示的是确认框,alert()显示的是警示框,prompt()显示的是对话框,open()用于打开新的窗口或者寻找已命名的窗口
arguments
-
函数内置一个对象 所有的实参都在这个对象上
-
函数名.length获取形参个数 arguments.length获取实参个数
-
arguments.callee代表arguments所在的函数 整个函数本身
if (arguments.length === arguments.callee.length) {
return num1 + num2; // return 10+undefined NaN
}
函数的定义
function定义的函数
可以先定义后调用 也可以先调用后定义
fn();
function fn() {
console.log('hi');
}
📌说一说JS变量提升?
分析:js代码执行 1预解析 2 执行 js的预解析:变量提升
预解析过程中,会把函数或者变量的声明提升 赋值不提升(赋值没有提升)
function XXX 及 var XXX
①变量提升是指在代码编译期,JS会把变量和函数的声明提升到代码的最前面。
②变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升
③变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。
④使用let和const声明的变量是创建提升,形成暂时性死区,在初始化之前访问let和const创建的变量会报错。
函数表达式
匿名函数-没有名字的函数 函数表达式必须先定义后调用
var fn2 = function() {
alert('爱全栈,爱生活');
};
fn2();
js作用域问题
作用域链:在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或者是抵达最外层的作用域(全局作用域)为止。
各个作用域的嵌套关系组成了一条作用域链。使用作用域链主要是进行标识符(变量和函数)的查询,标识符(变量和哈数)解析就是沿着作用域链一级一级地搜索标识符的过程而作用域链就是保证对变量和函数的有序访问。
作用域:管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。简单来说,作用域规定了如何查找变量。
静态作用域:函数定义位置就决定了作用域
var val = 1;
function test() {
console.log(val);
}
function bar() {
var val = 2;
test();
}
bar();
function foo(a) {
var b = a * 2
function bar(c) {
console.log(a, b, c);//2,4,12
}
bar(b * 3)
}
foo(2)
自由变量:在当前作用域中存在但未在当前作用域中声明的变量
一旦出现自由变量,就肯定会有作用域链,在根据作用域链查找机制,查找到对应的变量
查找机制:在当前作用域中发现没有该变量,然后沿着作用域链往上级查找,直到查到对应的变量为止,如果查找不到,直接报错
执行上下文:执行环境是动态的,作用域是静态的
例题:
function test(){
console.log(a);
}
test()
var a=1;//undefined
function test() {
console.log(a);
}
var a = 1;
test()//1
var a = 1
function fn1() {
function fn2() {
console.log(a)
}
function fn3() {
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn()//2
var a = 1;
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a;
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn()
该题 fn2
定义在函数 fn3
中,当 fn2
中找不到变量 a
时,会首先去 fn3
中查找,如果还查找不到,会到 fn1
中查找。本题可以在 fn3
中找到变量 a
,但由于 fn2()
执行时,a
未赋值,打印 undefined
。
预编译
函数声明整体提升,变量声明提升
函数表达式不能提升,var foo=function(){}
具名函数也不能提升 var foo=function bar(){}
声明:变量声明和函数声明 变量的声明优先于函数的声明,但是函数的声明会覆盖未定义的同名的变量
var a;
function a(){}
console.log(a);//[Function: a]
var a = 10;
function a() { }
console.log(a);//10
🅰️ 注意这个题:if(a)
为false,if内部不会执行,那test的AO中为什么还会有b啊?预编译并不是执行,它只不过把变量、函数等进行提升,只有在执行时,才会设计代码逻辑的判断。
function test() {
console.log(b);
if (a) {
var b = 100;
}
console.log(b);
c = 234;
console.log(c);
}
var a;
test();
a = 10;
console.log(c);
var foo = 1;
function bar() {
console.log(foo); //undefined
if (!foo) {
var foo = 10;//foo会进行提升
}
console.log(foo); //10
}
bar();
var a = 1
function foo() {
console.log(a);
}
foo()
变量的重复声明是无用的,但是函数的重复声明会覆盖前面的声明(无论是变量还是函数声明)
函数声明提升的优先级高于变量的声明提升(注意是优先级,指的是可以覆盖变量)
后面的函数声明会覆盖前面的函数声明
JS 监听对象属性的改变
defineProperty
Object.defineProperty() es5的方法 给对象定义属性
let obj = {};
Object.defineProperty(obj, 'x', {
configurable: true,//作用一:属性是否可以删除 true(可删)
value:100
});
delete obj.x;
console.log(obj.x);//undefined已经被删掉了
let obj = {};//给obj添加属性
let v = 'hi'; //在外面定义一个v get和set都可以操作
Object.defineProperty(obj, 'x', {
//作用1:属性是否可删除 如果值为true,delete可以删除属性(默认值),若为false 就删除不了属性
//作用2:是否可配置 能否加set和get方法
configurable: true,
enumerable: true, //是否可枚举 如果为false以后不能用for in 来迭代
writable: true, //是否可写 能不能重新赋值
// value: 100,用set和get就不用value了
set(newValue) {
console.log('赋值');
v = newValue;
},
get() {
console.log('取值');
return v;
}
});
obj.x = 200;//相当于设置值set
console.log(obj.x); //打印相当于获取值get
console.log(obj);//{ x: [Getter/Setter] } 但是如果enumerable: false不可枚举就不能打印了{}
//属性冻结 后 不可写 不可配置
let obj = { a: 1 };
Object.freeze(obj);
obj.a = 20;
console.log(obj.a); //1
//获取一个对象的属性描述器
console.log(Object.getOwnPropertyDescriptor(obj,'a'));
observer
数据改变,视图也改变--数据观察
let obj = {
name: 'djy',
age: 20,
company: {
address: 'beijing',
}
}
function observe(obj) {
for (let key in obj) {
let v = obj[key];
Object.defineProperty(obj, key, {
get() {
return v;
},
set(newV) {
console.log('数据更新');
v = newV;
}
});
}
}
observe(obj);
obj.company = {};
obj.company.address = 'shanghai'; //只能检测到一层,再里面的一层不会被检测到
代码优化:给对象的每个属性定义set和get
let obj = {
name: 'djy',
age: 20,
company: {
address: 'beijing',
}
}
function observe(obj) {
if (typeof obj !== 'object') return;
for (let key in obj) {
defineReactive(obj, key, obj[key]);
}
}
//定义响应式
function defineReactive(obj, key, value) {
observe(value);
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newV) {
if (typeof newV === 'object') {
observe(newV);
}
console.log('数据更新');
value = newV;
}
});
}
observe(obj);
注意:
obj.company.address = 'shanghai';//数据更新
obj.company = { a: 100 }; //数据更新
obj.company.a = 200;//数据更新
如果a是后来动态添加进去的a属性,代码就没有对后来的值做监听,所以没有数据更新
vue的响应式原理。这样,当每个属性发生改变时,就会进行数据检测
上述代码的缺点:
1.如果通过数组常用api来改变数组中的值 ,不会引起数据更新(vue重写数组api)
2.当数据量很大时,递归消耗性能
3.当属性不存在时无法监测
proxy
Object.defineProperty() 属性不存在无法监控
let obj = Object.freeze({ a: 10 });
//set 和value不能同时存在
//对于已经冻结的属性,Reflect.defineProperty不会报错,设置成功返回true 否则为false
let flag = Reflect.defineProperty(obj, 'a', {
set() {
},
get() {
}
});
console.log(flag); //false
let obj = {
name: 'djy',
age: 20
}
let proxy = new Proxy(obj, {
get(target, key) {
// 和return target[key];一样
return Reflect.get(target, key);
},
set(target, key, value) {
//代理对象,属性名,要改成什么值
console.log('数据更新');
// target[key] = value; 功能一样 给对象的属性赋值 但是可以得到一个返回值 true/false
return Reflect.set(target, key, value);
}
});
vue3中使用的是代理模式完成对象的改造
proxy.age=28
代理没有真实的属性,它改的就是原对象的属性,创建代理不会对原对象产生影响。只有通过代理修改的属性才是响应式的
get用来指定读取数据时的行为,它返回值就是最终读取到的值,指定get后再通过代理读取对象属性时,就会调用get方法来获取值
set会在通过代理修改对象时调用
读取和设置属性的行为都是调用方法 ,值修改之后做一些其他的操作
在vue中data()返回的对象会被vue所代理,vue代理该对象后,将其转换为响应式数据,响应式数据可以通过组件实例访问
vue中的vm.$data是实际的代理对象,通过vm可以访问到$data中的属性 vm.$data.msg=""和vm.msg=""效果一样
vue在构建响应式对象时,会同时将对象中的属性也做成响应式属性,叫深层响应式。如果想做浅层响应式,就import {shallowReactive} from vue
在数据方法中返回的是 return shallowReactive({msg:,stu:{}})
二、面向对象编程
ES5 对象
1.类和对象
对象是对特征和行为的封装,特征通过属性来表示,行为通过方法来指定
2.创建对象
-
对象直接量定义对象
var zs = { name: 'zhangsan', age: 18, study: function() { console.log('working hard'); }, play: function() { console.log('chiji playing'); } }; zs.sex = '男'; // 动态添加属性 zs.age = 33; // 修改属性
-
构造函数方式定义对象
var obj = new Object(); // new Array() obj.age = 31;//给空对象添加属性 obj.sex = '女'; obj.name = 'lucy'; // obj = {age:31,sex:'女'...} console.log(obj);
-
自定义构造函数 构造函数是一种特殊的函数,主要用来创建对象,总是与new一起
function Cat() { this.color = 'black'; } var c1 = new Cat(); // 类的实例 var c2 = new Cat();
📌new 构造函数(创建对象)步骤:
1 在内存中创建一个新的空对象 { }
2 让函数中的this指向空对象 this === {}
3 开始执行函数体 { age: 22, xueli: 'benke' }
4 返回该对象(构造函数中不需要写return)
3.构造函数
构造函数内部通过this添加的成员 叫实例成员 访问实例成员 对象.成员(实例成员不能通过构造函数访问)
访问静态成员 只能通过构造函数去访问
function Student(name, age) {
// name,age叫对象的属性(成员)
this.name = name;
this.age = age;
// study叫对象的方法 (成员)
this.study = function () {
console.log('day day up');
}
}
Student.nation = 'china'; // nation 静态成员 在构造函数本身添加的成员
console.log(Student.nation);
console.log(Student.age); // 实例成员不能通过构造函数访问
把公共的属性放到静态成员里
4.访问对象属性
从 ES5 开始,有三种方法可以获得对象的属性:
- for(let I in obj)该方法依次访问一个对象及其原型链中所有可枚举的类型
- Object.keys:返回一个数组,包括所有可枚举的属性名称
- Object.getOwnPropertyNames:返回一个数组包含不可枚举的属性
访问对象的成员 对象.成员或对象.方法()
var key = 'pname';zs[key];
zs['age'];或者 zs.age (直接.后面的age是一个属性,表示直接访问age属性,一定要在对象里能够找到)
(中括号里表示一个变量)
//访问对象的每一个属性(遍历) for in
for (var k in obj2) {
//console.log(k); // 对象的属性 key值
var v = obj2[k];
if (v instanceof Function) {//判断是否是函数的实例
v();
} else {
console.log(v);
}
}
var arr = [1, 3, 7]; // 数组对象
for (var k in arr) {
//console.log(k); // 在数组中 k表示下标
console.log(arr[k]);
}
5.原型
原型定义的函数是可共享(将来构造函数的实例都可以共享)
每一个构造函数都有一个prototype属性,指向一个对象,假如该对象定义了属性和方法这些都会被构造函数拥有
针对原型上的一个方法,不同的实例对象是一样的
function Student(name, age) {
this.name = name;
this.age = age;
}
var zs = new Student('zs', 22); // {name: 'zs', age: 22, study: ...}
var lisi = new Student('lisi', 19); // {name: 'lisi', age: 19, study: ...}
Student.prototype.run = function () {
console.log('running');
}
Student.prototype.study = function () {
console.log('day day up');
}
console.dir(Student.prototype);
zs.run();
lisi.run();
console.log(zs.study === lisi.study);//true
console.log(zs.run === lisi.run);//true
6.对象原型
对象都会有一个属性__proto__(非标准属性),指向构造函数的prototype原型对象
在 JS 中一个构造函数默认带有一个 prototype 属性,这个的属性值是一个对 象,同时这个 prototype 对象自带有一个 constructor 属性,这个属性指向这个构造 函数,同时每一个实例都会有一个_proto_属性指向这个 prototype 对象,我们可以把 这个叫做隐式原型,我们在使用一个实例的方法的时候,会先检查这个实例中是否有 这个方法,没有的话就会检查这个 prototype 对象是否有这个方法,
console.dir(zs.__proto__ === Student.prototype); // true
console.dir(lisi.__proto__ === Student.prototype); // true
console.log(zs.__proto__ === lisi.__proto__); // true
需要注意的一个问题:
Function.prototype.__proto__===Object.prototype
Function.__proto__===Function.prototype

7.constructor构造器
Student.prototype = {
constructor: Student, // 千万别忘记
study: function () {
console.log('good good study');
},
run: function () {
console.log('running');
}
}
构造函数原型protytype身上有一个属性constructor,它的值指向构造函数
Student.protytype
Student -----------------> 原型
<----------------
constructor
console.log(Student.prototype === zs.__proto__);
console.log(Student.prototype.constructor);
console.log(zs.constructor === Student);
console.log(zs.constructor === zs.__proto__.constructor);
console.log(zs.constructor === Student.prototype.constructor);
📌一道面试题
Function.prototype.a = 1;
Object.prototype.b = 2;
function A() {}
var a = new A();
console.log(a.a, a.b); // undefined, 2
console.log(A.a, A.b); // 1, 2
a是一个对象,不是函数的实例
所有函数都是Function的实例,函数也是对象
8.原型链
js成员查找机制-原型链查找机制
当我们访问一个对象的成员(方法或属性) 首先看自己有没有,假如没有就查找它的原型(也就是它的__proto__),假如还没有查找原型对象的原型(Object的原型对象),依次类推最终找到Object
9.构造函数的this指向问题
构造函数this指的是对象实例
原型对象函数里面也this也是指向实例对象 zs、ls
call() 可以实现函数调用 可以改变函数中this指向
function fun(a,b) {
console.log(this);//window.fun() 此时this === window
console.log(a,b);
}
fun.call();
function fun(a, b) {
console.log(this);
console.log(a, b);
}
var obj = {
num: 100
};
fun.call(obj,10,20);
使用call实现继承
属性继承
function Fa(name, age) {
console.log(this);
this.name = name;
this.age = age;
}
function Son(name, age) {
Fa.call(this, name, age); // Fa()
}
var zyc = new Son('张有才', 22);
console.log(zyc.name, zyc.age);
方法继承
bind的作用只实现改变this并不会发生函数调用,但是call会发生函数调用
10.ES5模拟实现类继承
https://juejin.cn/post/6844903477819211784#heading-2
function Fa(name, age) {
this.name = name;
this.age = age;
}
Fa.prototype.getMoney = function () {
console.log(1000000);
}
function Son(name, age) {
Fa.call(this, name, age);
}
//Son.prototype = Fa.prototype; //直接赋值不好,因为Son.prototype的constructor会指向Fa
Son.prototype = new Fa('wjj', 23); // {name:'wjj',age:23,__proto__}
Son.prototype.constructor = Son;
// 儿子自己专用的方法
Son.prototype.study = function () {
console.log('haohaoxuexi');
}
var zyc = new Son('张有才', 22);
console.log(zyc.name, zyc.age);
zyc.getMoney();
console.log(zyc.__proto__);
console.log(Fa.prototype);
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
❓将传入的对象作为创建的对象的原型
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
var child1 = new Child('kevin', '18');
console.log(child1);
ES6 类与对象
类里面所有函数不需要写function,多个函数之间不需要添加逗号分割
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。 通过 class 关键字,可以定义类。
定义
class创建类 new创建对象
class Student {
// 构造函数 接受传递来的参数 返回一个实例对象
constructor(sname, age) {
this.sname = sname;
this.age = age;
}
study() {
console.log(this.sname + '是学霸');
}
run() {
console.log('running');
}
}
var zs = new Student('zs', 22);
var lily = new Student('lily', 26);
console.log(zs.sname, zs.age);
console.log(zs.study === lily.study);
zs.run();
获取和设置属性
get和set方法
class Student {
// es7 此处定义的属性会定义在实例上
nation = 'china';
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
this.hobbies = ['basketball'];
}
// 设置属性
set hobby(hobby) {
this.hobbies.push(hobby);
}
// 获取属性 原型上的属性
get hobby() {
console.log(1);
return this.hobbies;
}
// 原型上的方法
study() {
console.log(this);
console.log('working hard!!!');
}
static eat(){//静态方法
console.log(this);
}
static get school(){//静态属性
return '全栈中心';
}
}
let s1 = new Student('zs', 22);
s1.hobby = 'football';
console.log(s1.hobby)
Student.eat(); // 调用静态方法
this在静态方法中 就是代表类本身,在原型方法中代表具体调用的实例
es6类本质上还是构造函数
类可以被实例化,但是不能直接调用
原型和自身的属性
自身属性是直接在对象上定义的。 而原型属性在 prototype 上定义。
function Dog(name) {
this.name = name;
}
Dog.prototype.numLegs = 4;//可以for遍历该属性
let beagle = new Dog("Snoopy");
let ownProps = [];
let prototypeProps = [];
for (let property in beagle) {
// console.log(property)包含原型上的属性(原型身上的属性和自己身上的属性)
// console.log(beagle)
if (beagle.hasOwnProperty(property)) {//自身的属性
ownProps.push(property)
} else {
prototypeProps.push(property)
}
}
console.log(ownProps);
console.log(prototypeProps)
class Dog {
constructor(name) {
this.name = name;
}
get numLegs() {//不可以for迭代
return 4
}
set numLegs(numLegs) {
numLegs = numLegs
}
}
let beagle = new Dog("Snoopy");
let beagle2 = new Dog("beagle2")
console.log(beagle.numLegs === beagle2.numLegs);//true
for (let property in beagle) {
console.log(property)//不包括原型上的属性
}
继承
class Fa {
constructor(name, age) {
this.name = name;
this.age = age;
}
getMoney() {
console.log(100000);
}
}
class Son extends Fa {
constructor(name, age) {
// this.name = name;
// this.age = age;
// 调用父类构造函数
super(name, age);
this.xueli = '研究生';
}
}
var son = new Son('wsc', 34);
son.getMoney();
console.log(son.age);
在继承中,实例化子类输出一个方法 先看子类有没有该方法 有就先执行子类的,假如子类里没有,则执行父类的
super.say()使用父亲的方法
es6类没有变量提升 所以必须先定义类 后实例化对象
使用细节
使用类中的属性时,要加this
方法中的this要看怎么去调,如果是实例对象调用的,那this就指向实例对象;如果是dom对象调用的,那就指向dom对象
有三种方法:
1.全局定义_this:将实例对象的this先保存下来,当点击事件发生时,调用 _this中的study中的方法
2.this.study.bind(this); bind改变this指向但不会立刻调用,只有点击事件发生后才会调用
3.箭头函数,this指向的是外部的this
<body>
<input type="button" value="按我" />
<script>
var _this;
class Student {
constructor(sname) {
_this = this;
this.sname = sname;
this.btn = document.querySelector('input');
this.btn.onclick = this.study.bind(this);
}
study() {
console.log(this.sname + 'work hard');
}
}
let s1 = new Student('jack');
s1.study();
console.log(_this === s1);
</script>
</body>
es6类的原理
使用es5模拟实现es6的给原型上添加方法的功能
for in 实例 可以迭代原型上的属性,但是不能迭代原型上的方法
// 定义在原型上的方法
function defineProperty(Constructor, protoProps) {
if (Array.isArray(protoProps)) {
for (let i = 0; i < protoProps.length; i++) {
let property = protoProps[i];
Object.defineProperty(Constructor.prototype, property.key, {
configurable: true,
enumerable: false,
writable: true,
...property
})
}
}
}
var Student = (function () {
function Student(name) {
// Student只能new 不能直接调用
if (!(this instanceof Student)) {
throw new Error('Class constructor Student cannot be invoked without "new"');
}
// 定义在类本身上的属性
this.name = name;
}
// 定义原型上的方法
defineProperty(Student, [{
key: 'study',
value: function () {
console.log('study');
}
}]);
return Student;
})();
JS内置对象
js对象:自定义对象 内置对象(Math、Date、String等) 浏览器对象
Math
// Math的属性
console.log(Math.PI);
console.log(Math.E);
// Math的方法
console.log(Math.abs(-1)); // 1
console.log(Math.ceil(9.1)); // 向上取整 10
console.log(Math.ceil(-9.1)); // -9
console.log(Math.floor(9.1)); // 向下取整 9
console.log(Math.floor(-9.1)); // -10
console.log(Math.max(10, 4, -12, 8));
console.log(Math.min(...[10, 4, -12, 8])); // es6展开运算符
console.log(Math.pow(2, 4)); // 2的4次方
console.log(Math.round(9.9)); // 四舍五入10
console.log(Math.round(-9.1)); // -9
console.log(Math.round(-9.9)); // -10
for (var i = 0; i < 100; i++) {
console.log(Math.random()); // >=0 && <1
}
Date
// Date构造函数
var now = new Date(); // 获取当前时间
var endTime = new Date('2022/1/1');
var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
// 日期对象的方法和属性
console.log(now.getFullYear()); // 年2020
console.log(now.getMonth()); // 月 3 默认0表示1月
console.log(now.getDate()); // 日 16
console.log(arr[now.getDay()]); // 4 0-6
console.log(now.getHours());
console.log(now.getMinutes());
console.log(now.getSeconds());
console.log(now.getMilliseconds());
// 2020年4月16日 22:30:16 星期几
// 当前时间的总毫秒数 时间戳 以下四种 1970.1.1-->指定时间
console.log(now.getTime());
//console.log(now.valueOf());
console.log(+new Date());
console.log(Date.now()); // H5新增 兼容
typeof Date.now() 'number'
String
重要的方法:
indexOf/lastIndexOf/charAt/charCodeAt(返回字母对应的ASCII码)/concat/
var index = str.indexOf('l', 3);//从哪开始找
startwith() 参数有 3 个,stringObj,要搜索的字符串对象,str,搜索的字符串,position,可选,从哪个位置开始搜索,如果以 position 开始的字符串以搜 索字符串开头,则返回 true,否则返回 false
Indexof 函数可返回某个指定字符串在字符串中首次出现的位置
substr 字符串截取 从哪开始截取 截几个 (长度)
slice 从哪开始截 到哪结束 不包含末尾位置
substring 基本上与slice一样 substring内部会把参数小的作为起点
replace找到第一个符合条件的进行替换 后面就不找了
split() 字符串————>数组 以什么分割
String.fromCharCode() 静态方法
RegExp正则表达式
匹配字符串中字符组合的模式
1.正则表达式简介
创建对象、test检测对象
// RegExp
var regexp = new RegExp(/456/);
console.log(regexp);
// 直接量
var regexp2 = /abc/;
// 正则表达式对象方法 test() 检测某字符串是不是符合规则
// 返回值true表示有符合规则的字符组合
console.log(regexp2.test('abcfg'));//true
console.log(regexp2.test('bacfg'));//false
console.log(regexp.test(456));
2.正则特殊符
正则表达式的特殊字符
边界符
^ 匹配行首的文本 以谁开头
$ 匹配行尾的文本 以谁结束
字符类 [] 表示有一系列字符可以选择 只需要匹配一个就可以
量词类
*
+
?
{n}
{n,}
{n,m}
var reg = /asd/; // 只要包含asd这个字符串就返回true
console.log(reg.test('asdgfd'));//true
var reg2 = /^asd/;//要以asd开头
console.log(reg2.test('asdgfd'));
console.log(reg2.test('gasdfd'));
var reg3 = /^asd$/; //精确匹配
console.log(reg3.test('asdgfd'));//false
console.log(reg3.test('gasdfd'));//false
console.log(reg3.test('asd'));//true
3.字符类
var rg = /[xyz]/; // 字符串有xyz三个其中一个字符
console.log(rg.test('abcd')); // false
console.log(rg.test('abxcd')); // true
console.log(rg.test('abycd')); // true
console.log(rg.test('xabcdz')); // true
var rg2 = /^[xyz]$/; // 只能是x或者y或者z
console.log(rg2.test('xx')); // false
console.log(rg2.test('x')); // true
console.log(rg2.test('xy')); // false
console.log(rg2.test('z')); // true
var rg3 = /^[0-9a-z]$/; // - 范围
console.log(rg3.test(3));
console.log(rg3.test('a3')); // false
// 中括号的^表示取反
var rg4 = /^[^0-9a-z]$/;
console.log(rg4.test('5')); // false
console.log(rg4.test('a')); // false
console.log(rg4.test('A')); //true
4.量词
var rg = /^c*$/; // *表示可以出现0次或多次 >=0 {0,}
console.log(rg.test(''));//true
console.log(rg.test('c'));//true
console.log(rg.test('cc'));//true
console.log(rg.test('adfg'));//false
var rg = /^c+$/; // +表示可以出现1次或多次 >=1 {1,}
console.log(rg.test('')); // false
console.log(rg.test('c')); // true
console.log(rg.test('cc')); // true
console.log(rg.test('adfg')); //false
var rg = /^c?$/; // +表示可以出现1次或0次 0 || 1 {0,1}
console.log(rg.test('')); // true
console.log(rg.test('c')); // true
console.log(rg.test('cc')); // false
console.log(rg.test('adfg')); //false
var rg = /^c{3}$/; // 重复3次
console.log(rg.test('')); // false
console.log(rg.test('c')); // false
console.log(rg.test('cc')); // false
console.log(rg.test('ccc')); // true
var rg = /^c{3,}$/; // 至少3次
console.log(rg.test('')); // false
console.log(rg.test('c')); // false
console.log(rg.test('cc')); // false
console.log(rg.test('ccccc')); // true
var rg = /^c{3,5}$/; // 至少3次,最多5次
console.log(rg.test('')); // false
console.log(rg.test('c')); // false
console.log(rg.test('ccc')); // true
console.log(rg.test('cccccc')); // false
5.正则验证用户名
var rg = /[1]{6,16}$/;
rg.test(this.value)
6.正则中括号使用总结
var rg = /^[xyz]$/; // 只能是x或y或z
var rg2 = /^xyz{3}$/;//只对z出现3次
console.log(rg2.test('xyzxyzxyz'));//false
console.log(rg2.test('xyzzzz'));//false
var rg3 = /^(xyz){3}$/;//让xyz整体出现3次 优先级别
console.log(rg3.test('xyzxyzxyz'));//true
console.log(rg3.test('xyzxyzxyzxyz'));//false
7.预定义类
预定义类
\d === [0-9]
\D === [^0-9]
\w === [a-zA-Z0-9_]
\W === [^a-zA-Z0-9_]
\s === [\t\r\n\v\f] 匹配空格
\S === [^\t\r\n\v\f] 匹配非空格字符
案例 验证座机号码
010-12345678 \d{3}-\d{8}
0551-64587451
8.正则替换与参数
<textarea name="" id="msg" cols="30" rows="10"></textarea>
<button>提交</button>
// replace() stringObj.replace(substr/rg,replacement)
var str = 'hello world';
//var newStr = str.replace('hello','hi');
var newStr = str.replace(/hello/, 'hi');
console.log(str);
console.log(newStr);
var btn = document.querySelector('button');
var tarea = document.querySelector('textarea');
btn.onclick = function () {
// g全局匹配 i忽略大小写 gi
tarea.value = tarea.value.replace(/激情|gay/gi, '**')
}
9.关于正则表达式 \1 \2之类的问题
我们创建一个正则表达式
var RegExp = /^(123)(456)\2\1$/;
这个正则表达式匹配到的字符串就是
123456456123
创建另外第二正则表达式
var RegExp1 = /^(123)(456)\1$/;
这个正则表达式匹配到的字符串是
123456123
创建另外第三正则表达式
var RegExp1 = /^(123)(456)\2$/;
这个正则表达式匹配到的字符串是
123456456
这个\1 \2...... 都要和正则表达式集合()一起使用
简单的说就是
\1表示重复正则第一个圆括号内匹配到的内容
\2表示重复正则第二个圆括号内匹配到的内容
$1表示第一个分组里的值l
基本数据类型与引用数据类型
基本数据类型 按值访问,可操作保存在变量中的实际的值 number string boolean null undefined symbol
引用数据类型 Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、 Boolean)以及单体内置对象(Global、Math)等
1 基本数据类型不能添加动态属性 引用数据类型可以动态添加属性
2 传参 假如传入的实参是一个基本类型 实际上传的是值的拷贝,引用数据类型传递的是地址
补充问题
Object.getPrototypeOf()返回给定对象的原型
在ES5中,如果传递给方法的参数不是对象,则会抛出TypeError异常。
在ES6中,如果传递给方法的参数不是对象,则会强制类型转换为对象。
console.log(Object.getPrototypeOf(Date) === Date.prototype) // false
console.log(Object.getPrototypeOf(Date()) === String.prototype) // true
console.log(Object.getPrototypeOf(new Date()) === Date.prototype) // true
console.log(Object.getPrototypeOf(Function) === Function.prototype) // true
console.log(Object.getPrototypeOf(Function()) === Function.prototype) // true
console.log(Object.getPrototypeOf(new Function()) === Function.prototype) // true
console.log(Object.getPrototypeOf(Object()) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object) === Function.prototype) // true
console.log(Object.getPrototypeOf(new Object()) === Object.prototype) // true
console.log(Object.getPrototypeOf({}) === Object.prototype) // true
console.log({} == new Object()) // false
三 、函数(进阶)
函数定义方式
function ***() {}
通过构造函数方式(少见)
// Function里面参数都是字符串格式 参数,函数体内部逻辑
var f = new Function('a', 'b', 'console.log(a+b)');
f(2, 5);
f.index = 10;
console.log(f.__proto__ === Function.prototype);
console.log(f instanceof Object);
console.log(f.index);
📌this指向
this永远指向最后调用它的那个对象
- 普通函数调用 ,函数中this指向window
- 调用对象上的方法,函数中this指向该对象
- 构造函数中this指向该实例对象
- 定时器函数中this指向window
- 立即执行函数中的this指向window
function fn() {
console.log(this); //
console.log('hello everyone');
}
//开启严格模式后,函数内部this指向undefined,但全局对象window不会受影响
fn(); // fn.call() 普通函数调用 则函数中this === window
var obj = {
sayHello: function () {
console.log(this);
console.log('hi');
}
}
obj.sayHello(); // 对象调用 函数中this === 该对象
function Student() {
}
new Student(); // 构造函数调用 this === 实例对象
/*
btn.onclick = fun // 通过事件触发 this指的是绑定事件对象
*/
/* 定时器函数*/
setInterval(function () {
console.log(this); // window
}, 1000);
(function () {
console.log(this); // 立即执行函数中this === window
console.log(11);
})();
原型上的方法,对象的实例
例题:
a = 1;
function foo() {
console.log(this.a);
}
const obj = {
a: 10,
bar() {
foo(); // 1
}
}
obj.bar();
foo
虽然在obj
的bar
函数中,但foo
函数仍然是独立运行的,foo
中的this
依旧指向window
对象。
var a = 1
function outer () {
var a = 2
function inner () {
console.log(this.a) // 1
}
inner()
}
outer()
在function内部, 加var的是局部变量, 不加var的则是全局变量;这个题与上题类似,但要注意,不要把它看成闭包问题
a = 1
var obj = {
a: 2,
foo() {
console.log(this.a)
}
}
var foo = obj.foo;
obj.foo();//2
foo();//1
只要fn前面什么都没有,肯定不是隐式绑定。
var obj = {
a: 1,
foo() {
console.log(this.a)
}
};
var a = 2;
var foo = obj.foo;
var obj2 = { a: 3, foo: obj.foo }
obj.foo();//1
foo();//2
obj2.foo();//3
function foo() {
console.log(this.a)
}
function doFoo(fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
用函数预编译的知识来解答这个问题:函数预编译四部曲前两步分别是:
- 找形参和变量声明,值赋予
undefined
- 将形参与实参相统一,也就是将实参的值赋予形参。
obj.foo
作为实参,在预编译时将其值赋值给形参fn
,是将obj.foo
指向的地址赋给了fn
,此后fn
执行不会与obj
产生任何关系。fn
为默认绑定。
Window {…}
2
function foo() {
console.log(this.a)
}
function doFoo(fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
console.log(this)
:obj2.doFoo
符合xxx.fn
格式,doFoo
的为隐式绑定,this
为obj2
,打印{a: 3, doFoo: ƒ}
fn()
: 没有于obj2
产生联系,默认绑定,打印2
{a: 3, doFoo: ƒ}
2
var name='zcxiaobao';
function introduce(){
console.log('Hello,My name is ', this.name);
}
const Tom = {
name: 'TOM',
introduce: function(){
setTimeout(function(){
console.log(this)
console.log('Hello, My name is ',this.name);
})
}
}
const Mary = {
name: 'Mary',
introduce
}
const Lisa = {
name: 'Lisa',
introduce
}
Tom.introduce();
setTimeout(Mary.introduce, 100);
setTimeout(function(){
Lisa.introduce();
},200);
Tom.introduce()执行: console位于setTimeout的回调函数中,回调函数的this指向window
Mary.introduce直接作为setTimeout的函数参数(类似题目题目3.3),会发生隐式绑定丢失,this为默认绑定
Lisa.introduce执行虽然位于setTimeout的回调函数中,但保持xxx.fn模式,this为隐式绑定。
Window {…}
Hello, My name is zcxiaobao
Hello,My name is zcxiaobao
Hello,My name is Lisa
name = 'javascript' ;
let obj = {
name: 'obj',
A (){
this.name += 'this';
console.log(this.name)
},
B(f){
this.name += 'this';
f();
},
C(){
setTimeout(function(){
console.log(this.name);
},1000);
}
}
let a = obj.A;
a();
obj.B(function(){
console.log(this.name);
});
obj.C();
console.log(name);
javascriptthis
javascriptthis
javascriptthis
javascriptthis
📌apply call bind
改变函数内部this指针的指向函数
// call() apply() bind()
function fn(a, b) {
console.log(this);
console.log(a, b);
}
var obj = {
a: 10
}
//fn.call(obj,1,2);
// apply()
//fn.apply(obj,[1,2]);
var arr = [10, 12, 45, 38];
var max = Math.max.apply(null, arr);
console.log(max);
// bind() bind()可以改变函数中this指向 但它不会调用函数 bind返回是函数
// 返回由指定this值和初始化参数改造的原函数拷贝
var newFn = fn.bind(obj, 1, 3);
newFn();
案例
<body>
<button id='btn'>接受验证码</button>
<script>
var btn = document.querySelector('#btn');
var num = 10;
btn.onclick = function() {
//var that = this;
this.innerHTML = '剩余时间'+num + "s";
setInterval(function() {
num--;
this.innerHTML = '剩余时间'+num + "s";
}.bind(this),1000);
}
</script>
</body>
模拟call原理
缺点:没法删除多引入的fun属性
this不能出现在赋值号的左边
eval的功能是将对应的字符串解析成 JS 并执行,应该避免使用 JS,因为非常消耗性能 (2 次,一次解析成 JS,一次执行)
//this->fn context->obj/包装'hello'
Function.prototype.call = function (context, ...args) {
//context = context instanceof Object ? context : Object(context)
//确保context一定是一个对象
// 防止context没传那么默认window
context = context ? Object(context) : window
// 1.改变fn里的this 让fn中的this指向context
// 在这个对象中添加一个数据fun
context.fun = this;
// 2.执行fn
let r = context.fun(...args)//context调用这个fn函数所以this指向了context
delete context.fun
return r
}
function fn(a, b, c) {
console.log(this);
console.log(arguments);
return 10;
}
let obj = { a: 1 };
let res = fn.call(obj, 1, 3, 5);
console.log(res)
context对象上加了一个属性,这个属性指向这个函数。
理解fn.call.call.call(obj, 1, 2, 3)问题:
fn.call.call拿到的都是call函数本身,只有最后一次是真正的调用(报错)
Function.prototype.call = function (context, ...args) {
context = context ? Object(context) : window
context.fun = this;
let r = context.fun(...args)
delete context.fun
return r
}
function fn(a, b, c) {
console.log(this);
console.log(arguments);
return 10;
}
function fn2() {
console.log(2);
}
fn.call.call.call(fn2, 1, 2, 3);//2 函数fn2执行
模拟apply
Function.prototype.apply = function (context, args) {
context = context ? Object(context) : window
context.fun = this;
// 2.执行fn
let r = context.fun(...args)//context调用这个fn函数所以this指向了context
delete context.fun
return r
}
function fn(a, b, c) {
console.log(this);
console.log(arguments);
return 10;
}
let obj = { a: 1 };
let res = fn.apply(obj, 1, 3, 5);
console.log(res)
模拟bind
返回的是一个新函数
Function.prototype.bind = function (context, ...args) {
let that = this;
return function () {
return that.call(context, ...args)
}
};
function fn(a, b) {
console.log(this);
console.log(arguments);
}
let obj = { a: 1 }
let newFn = fn.bind(obj, 1, 2)
newFn()
参数可以分两次传
Function.prototype.bind = function (context, ...args) {
let that = this;
function fnBound(...arg2) {
return that.call(this instanceof fnBound ? new that : context, ...[...args, ...arg2])
}
return fnBound;
}
function fn(a, b) {
console.log(this);
console.log(arguments);
}
let obj = { a: 1 }
let newFn = fn.bind(obj, 3)
new newFn(2)
严格模式
在严格的条件下执行js代码 ie10以上
严格模式一些变化
-
严格模式禁止变量没有声明就赋值 变量必须先定义后赋值
-
禁止删除已经声明的变量 delete num
-
严格模式下 全局作用域中函数this是undefined 构造函数不加new this会报错
-
严格模式下 参数名不能重复
-
不允许在非函数代码块中定义函数
高阶函数
函数的参数是函数或者返回值是函数
📌闭包
作用域:变量在某个范围内有效 全局作用域 局部作用域(函数作用域)
y没有通过var声明的变量 隐式全局变量
作用域链:如果自己定义了var 就用自己的,如果自己没定义var,就向上一层一层的去找上一层是否定义
闭包:延伸变量作用域,在函数外面也可以用里面的变量,有权利访问另外一个函数作用域中的变量的函数
闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。
❓如何理解闭包的应用1、模仿块级作用域。2、保存外部函数的变量。3、封装私有变量
// fun闭包函数 fun2闭包
function fun() {
var a = 10;
function fun2() {
console.log(a);//10
}
//fun2(); 10
return fun2;
}
var f = fun();
f(); // 闭包可以延伸变量作用范围
闭包案例
<body>
<ul class="nav">
<li>北京</li>
<li>上海</li>
<li>深圳</li>
</ul>
<script>
// 单击li 打印每个li的序号
var lis = document.querySelector('ul.nav').querySelectorAll('li');
/* 思路一 动态添加属性
for(var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function() {
console.log(this.index);
}
}
*/
/* 思路二闭包
for(var i = 0; i < lis.length; i++) {
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
*/
/* es6 let 形成块级作用域
for(let i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
console.log(i);
}
}
*/
</script>
</body>
for循环是同步代码
定时器代码和时间绑定代码全是异步代码
📌深复制和浅复制
// 浅复制 更深层次对象级别只拷贝引用
var obj1 = {
a: 10,
b: 20,
info: {
c: 30
}
};
var target = {};
for(var key in obj1) {
//console.log(key);
//console.log(obj1[key]);
target[key] = obj1[key];
}
target.info.c = 100;
console.log(obj1.info.c);
console.log(target.info === obj1.info); // true
var obj = { a: 1, b: 2, c: [6, 6, 6, 6], d: { name: 'tom' } }
// 深拷贝
function deepCopy(target, source) {
for (var key in source) {
var item = source[key]; //key=c item=[6,6,6,6]
// 判断是否为数组
if (item instanceof Array) {
target[key] = []; //target={a:1,b:2,c:[]}
deepCopy(target[key], item);//deepCopy([],item) 参数:结果集,要复制的目标
} else if (item instanceof Object) {
// 判断是否为对象
target[key] = {};
deepCopy(target[key], item);
} else {
// 简单数据类型
target[key] = item;
}
}
}
deepCopy(target, obj);
console.log(target);
console.log(target.c === obj.c);
循环引用
function deepClone(obj, hash = new WeakMap()) {
//null或undefined
if (obj == null) return obj;
//number string boolean symbol typeof检测一个变量的类型
if (typeof obj !== 'object') return obj;
//日期
if (obj instanceof Date) return new Date(obj);
//正则
if (obj instanceof RegExp) return new RegExp(obj);
//数组或者普通对象
if (hash.get(obj)) {
console.log(hash.get(obj));
return hash.get(obj);
}
let newObj = new obj.constructor;
//保存上一次结果的值,防止一直赋值进入死循环,
//键是要拷贝的对象,值为上一次拷贝的结果,第一次hash也可能为空,如果已经拷贝过就不要再拷贝了
hash.set(obj, newObj);
for (let key in obj) {
newObj[key] = deepClone(obj[key], hash); //递归
console.log(newObj[key]);
}
return newObj;
}
//循环引用
let obj = { a: 1 };
obj.b = obj;
let res = deepClone(obj);
console.log(res);
//Maximum call stack size exceeded报错 栈空间不够
/*
newobj
{}
newObj[key]
1
(hash.get(obj)
{ a: 1 }
newObj[key]
{ a: 1, b: [Circular] }*/
四、ES6
1.变量与常量
var的缺点
重复定义同一个变量不会报错,会发生变量值覆盖
存在变量提升机制
污染全局作用域
不能定义常量
var a = 10;
console.log(a);
console.log(window.a);
console.log(a === window.a);
let声明变量
不存在变量提升;不允许重复定义;
不会挂载到window上,不会污染全局作用域;
let定义的变量具有块级作用域,会形成暂时性死区:在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。
let num = 1;
{
//{}和里面的num进行了绑定,所以不会找外面的num
console.log(num);
let num = 2;
//报错:认定了这里也有num所以不能先调用后定义
}
可以用来解决异步循环问题
var+闭包(定义立即执行函数)函数可以形成自己的独立作用域,当执行的时候各自找各自作用域的变量对应的值,所以当定义一个定时器,定时器执行的时候就可以找到对应自己的i值
let可以形成独立块级作用域,所以直接在for循环中定义定时器即可
const声明变量
不能给常量赋值,但是可以修改引用类型,不能赋值是指不能重新赋值修改
但是引用数据类型修改值并没有改变他的地址值,是可以的
除非值会发生改变否则全部都用const
2.解构赋值
对象解构
//想取哪个值就把哪个在左边写出来,但必须是存在的 否则undefined
const { name, age } = {
name: 'djy',
age: 20
};
//可以换名字 用冒号隔开,但原名字作废
//设定默认值,如果下文有就用下文的,如果没有就用默认值
const { name: tname, age, tel = '13988231267' } = {
name: 'djy',
age: 20,
tel: '19923456123'
};
数组解构
//解构对于对象和数组都成立
const [city, year] = ['北京', 2022];
console.log(city, year);
嵌套解构
对象里面套用数组,或者数组里套用对象都可以
不能破坏对象或数组中的结构,一一对应的关系
const { user: { uname, age } } = {
user: {
uname: 'zs',
age: 18
}
}
console.log(uname, age); //直接写对应属性的值就可以
剩余运算符
//剩余运算符,只能在最后
let [city, ...arr] = ['北京', 2022, '中国'];
console.log(arr); //是一个数组
//可以为空,逗号不能为空
let [, ...arr] = ['北京', 2022, '中国'];
console.log(arr); //是一个数组 [ 2022, '中国' ]
//对象是无序属性的集合
let { sex, ...info } = {
name: 'zs',
age: 18,
sex: '男'
}
console.log(info); //{ name: 'zs', age: 18 }
console.log(sex); //男
//求n个数的和
function getSum(...arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
console.log(getSum(1));
console.log(getSum(1, 7, 6));
console.log(getSum(1, 7));
展开运算符
//数组的展开
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr = [...arr1, ...arr2];
console.log(arr);
//关于对象的展开
let obj1 = { a: 1 };
let obj2 = { b: 2 };
let newOjb = {...obj1, ...obj2 };
console.log(newOjb);
//数组和对象的混合
let obj1 = { a: 1 };
let obj2 = { b: [10, 20] };
let newOjb = {...obj1, ...obj2 };
console.log(newOjb); //{ a: 1, b: [ 10, 20 ] }
//关于展开运算符的应用
console.log(Math.max(...[1, 2, 3, 4])); //参数列表不能是数组,所以对数组进行展开
let arr = [10, 20, 30];
console.log(Math.max(...arr));
3.字符串
模板字符串
更容易拼接字符串
let str = `hello`;
let str2 = `world`;
let str3 = `${str}${str2}`
let zs = {
name: 'zs',
age: 20,
sex: '男',
sayHi() {
return 'hi';
}
};
let domStr = `
<div>
<span>${zs.name}</span>
<span>${zs.age}</span>
<span>${zs.sex}</span>
<span>${zs.sayHi()}</span>
</div>
`;
console.log(domStr);
let name = 'djy';
let age = '23';
function desc(strings,...values) {
// 第一个参数 模版字符串每个部分的字符
// 第二个参数开始 都是模版字符串变量的值
console.log(strings)//[ '', '今年', '岁啦' ]
console.log(values)//[ 'djy', '23' ]
console.log(arguments);//[Arguments] { '0': [ '', '今年', '岁啦' ], '1': 'djy', '2': '23' }
}
// 带标签的模版字符串
desc`${name}今年${age}岁啦`;
let name = 'zs';
let age = '28';
function desc(strings, ...values) {
console.log(strings);//[ '', ' is ', ' old! ', '' ]
console.log(strings.length);
console.log(values);//[ 'zs', '28', '28' ]
let str = '';
for (let i = 0; i < values.length; i++) {
str += strings[i] + values[i].toUpperCase();
}
str += strings[strings.length - 1];
return str;
}
console.log(desc `${name} is ${age} old! ${age}`);
let a = 6;
let b = 9;
function simpleTag(strings, ...expressions) {
console.log(strings);
for (const expression of expressions) {
console.log(expression);
}
return 'foobar';
}
let taggedResult = simpleTag`${a} + ${b} = ${a + b}`;
console.log(taggedResult)
字符串新增方法
// includes() startsWith() endsWith()
console.log('ilife'.includes('i')); // true
console.log('ilife'.startsWith('im'));//false
console.log('ilife'.endsWith('fe'));//true
console.log('x'.repeat(3));
console.log(String.raw`first line\nsecond line`);//first line\nsecond line
console.log(`first line\nsecond line`);
/*
first line
second line
*/
4.箭头函数
普通函数function fn() { }
函数表达式let fn = function() {}
箭头函数 用箭头把参数与函数体连接一起 不用写function let fn = () => {}
📌箭头函数和 function 有什么区别
箭头函数根本就没有绑定自己的 this,在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用
var a = 11;
function test2() {
this.a = 22;
let b = () => {
console.log(this.a)
}
b();
}
var x = new test2();
const a = 100;
let obj = {
a: 10,
fn: () => {
console.log(this.a===window.a);//true
console.log(this.a)//undefined
}
}
obj.fn();
箭头函数没有arguments,可用…rest 参数获取
let func = (...rest) => {
console.log(rest)
//[1,2,3]
}
func(1,2,3)
不能通过 new 关键字调用,同样也没有 new.target 值和原型
5.数组
push pop unshift shift splice slice
every map foreach sort concat join some filter reduce indexOf charAt reverse
arguments 是类数组对象,有 length 属性,不能调用数组方法 可用 Array.from()转换
Array.from()把一个数组或类数组变成数组 会复制一份
function fn(){
//arguments [Arguments] { '0': 1, '1': 2, '2': 3 }
console.log([...arguments]);//[1,2,3]
}
fn(1,2,3)
function fn() {
let arr=Array.from(arguments)
console.log(arr);
}
fn(1, 2, 3)
//自定义类数组
let obj = {
0: 1,
1: 10,
2: 20,
length: 3
}
let arr = Array.from(obj)
console.log(arr);
console.log(Array(3));//[ <3 empty items> ] 此时的3是数组长度
console.log(Array.of(3));//[ 3 ] 3作为数组元素
find返回数组中满足条件的第一个元素的值
let arr = [1, 3, 5, 7, 9]
let res = arr.find((item, index, arr) => {
return item === 5
})
console.log(res);
fill 填充什么,序号从1到3(不包含3)
arr.fill('b',1,3)
6.对象(补充)
Object.is(===)判断两个对象是否相等
Object.assign() 合并 浅拷贝 如slice
let nameObj = {
name: 'djy'
}
let ageObj = {
age: 20
}
let obj = {}
Object.assign(obj, nameObj, ageObj)
console.log(obj);
let arr = [1, 2, 3, [4]];
let resArr = arr.slice(3); // [[4]]
resArr[0][0] = 10;
console.log(arr[3]);
let nameObj = {
name: 'djy'
}
let ageObj = {
age: 20
}
let obj = {}
Object.setPrototypeOf(obj, nameObj) // obj.__proto__=nameObj
console.log(Object.getPrototypeOf(obj));//{ name: 'djy' }
给对象动态添加属性
let obj = {}
Object.defineProperty(obj, 'a', {
value: 1
})
console.log(obj.a)//1
属性访问器
let obj = {};
let _v = 'hello'
Object.defineProperty(obj, 'a', {
//value: 1
get() {
return _v;
},
set(newValue) {
_v = newValue;
}
});
obj.a = 'hi';
console.log(obj.a);
let obj = {};
let _v = 'hello'
Object.defineProperty(obj, 'a', {
//enumerable: false, // 是否可被迭代 默认是false
//configurable: true, // 是否可配置 默认是false(是否可以被删除)
//writable: true,
//value: 1
// get() {
// return _v;
// },
// set(newValue) {
// _v = newValue;
// }
});
7.set&&map
set
新的数据结构 Set 没有重复项
let s=new Set();
new Set([1,2,3,4]) 接受数组参数
new Set([1,2,3,4,3]) 可以去重
方法 size/add/delete/has/clear/forEach
集合可迭代,循环拿到每个值,
数组去重可以通过set但是得到的结果是集合,如何进行操作使得去重后仍是数组呢?
//对所有可迭代的 都采用展开运算符进行操作
let res = [...s3];
console.log(res); //[ 1, 2, 3 ]
// s3中的每一个元素都会触发调用一次匿名函数
s3.forEach(function(item) {
console.log(item); //1 2 3
});
let s = new Set();
s.add(1).add(2);
console.log(s); //Set { 1, 2 }
let r = s.delete(1); //把元素1删掉
console.log(s); //Set { 2 }
console.log(r); //true判断是否删除成功
console.log(s.has(12)); //false是否含有
s.clear();//清空集合中的元素
console.log(s); //Set {}
let s3=new Set(['html','css','js'])
console.log(s3.keys());//[Set Iterator] { 'html', 'css', 'js' }
console.log(s3.values());//[Set Iterator] { 'html', 'css', 'js' }
console.log(s3.entries());//[Set Entries] { [ 'html', 'html' ], [ 'css', 'css' ], [ 'js', 'js' ] }
weakset 只能放对象
map
//map hash表 散列
//key不重复 可以存键值对 key value
let map = new Map();
// map.set();
map.set('a', 123);
map.set('a', 456);
map.set('b', 123);
console.log(map);
//Map { 'a' => 456, 'b' => 123 }
let map = new Map();
var Student = { x: 1 };
map.set({ x: 1 }, 123);
map.set(Student, 123);
map.set({ x: 1 }, 456);
//对象相当于开辟了不同的内存空间所以不是同一个
map.set('b', 123);
// console.log(map);
//Map { { x: 1 } => 123, { x: 1 } => 123, { x: 1 } => 456, 'b' => 123 }
console.log(map.get('b')); //取值
// console.log(map.get({ x: 1 }));//undefined 因为这里的{x:1}又是一个新的对象了
//用变量接收
console.log(map.get(Student));
8.模块化
import导入 export导出
1 export let a = 10; import {a } from '***'
2 export { a, b }
import {a,b } from '***'
import * as obj from '***'
3 export default ***
import a from '***'
注意:异步问题也是es6部分,单独拿出来作为一章
五、异步
高阶函数解决异步问题
发布订阅模式和观察者模式
【发布订阅模式】发布订阅模式是指基于一个事件(主题)通道,希望接收通知的对象 Subscriber 通过自定义事件订阅主题,被激活事件的对象 Publisher 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
发布订阅模式中有三个角色,发布者 Publisher ,事件通道 Event Channel ,订阅者 Subscriber 。
以目前的热播剧开端为例,临近过年,摸鱼的心思越来越重,每天就迫不及待的等开端更新,想在开端更新的第一刻就开始看剧,那你会怎么做那?总不能时时刻刻刷新页面吧。平台提供了消息订阅功能,如果你选择订阅,平台更新开端后,会第一时间发消息通知你,订阅后,你就可以愉快的追剧了。
上面案例中,开端就是发布者 Publisher
,追剧人就是订阅者 Subscribe
,平台则承担了事件通道 Event Channel
功能。
【观察者模式】当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。在观察者模式中,只有两种主体:目标对象 (Object
) 和 观察者 (Observer
)
比如你要应聘阿里巴巴的前端工程师,结果阿里巴巴 HR 告诉你没坑位了,留下你的电话,等有坑位联系你。于是,你美滋滋的留下了联系方式。殊不知,HR 已经留下了好多联系方式。好在 2022 年 2 月 30 号那天,阿里巴巴有了前端工程师的坑位,HR 挨着给留下的联系方式联系了一通。
案例中阿里巴巴就是目标对象 Subject
,联系方式列表就是用来维护观察者的 observerList
,根据前端职位的有无来调用 notify
方法
Promise核心应用实现
没有 promise,可以用回调函数代替
基本使用
- new Promise时传入一个函数(excutor)作为参数,该函数有两个参数resolve和reject,这两个参数也是函数
- executor会立即执行(立即执行函数)
- 当new Promise默认创建一个实例时,不是成功态也不是失败态,内部会维护自己的状态,默认状态是等待态pending
- 在Promise内部resolve();将状态改为成功态fulfilled reject();将状态改为失败态rejected 二者互斥,只能有一个状态,不可逆,不可相互转化 throw new Error();是失败态
- 实例身上有then方法,传两个函数作为参数 高阶函数,第一个函数是成功状态时调用,第二个函数是失败状态时调用
- 高阶函数:参数是一个函数或者返回值是一个函数
实现:先搭一个简单的架子
const Promise = require("./Promise")
const p = new Promise((resolve, reject) => {
console.log(123);
})
let PENDING = 'PENDING'
let FULFILLED = 'FULFILLED'
let REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
let resolve = () => {
}
let reject = () => {
}
executor(resolve, reject)
}
then() {
}
}
module.exports = Promise
实现executor中简单的同步逻辑
const Promise = require("./Promise")
const p = new Promise((resolve, reject) => {
console.log(123);
resolve('value')
})
p.then((data) => {
console.log(data);
}, (reason) => {
console.log('fail');
})
let PENDING = 'PENDING'
let FULFILLED = 'FULFILLED'
let REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
let resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.value = value
}
}
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED
this.reason = reason
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
if (this.state == FULFILLED) {
onFulfilled(this.value)
} else if (this.state == REJECTED) {
onRejected(this.reason)
}
}
}
module.exports = Promise
支持异步
异步因为执行then的时候还没确定他的状态以致于无法得到异步执行的结果,定义两个数组,一个用于存放异步成功函数,另一个用于存放异步失败。当调用resolve或者调用reject函数的时候异步逻辑肯定已经有了状态,所以把
const Promise = require("./Promise")
const p = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(">0.5")
} else {
reject("<=0.5")
}
}, 1000)
})
p.then((data) => {
console.log('success',data);
}, (reason) => {
console.log('fail',reason);
})
let PENDING = 'PENDING'
let FULFILLED = 'FULFILLED'
let REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this.resolveCallback = []
this.rejectCallback = []
let resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.value = value
this.resolveCallback.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED
this.reason = reason
this.rejectCallback.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
if (this.state == FULFILLED) {
onFulfilled(this.value)
} else if (this.state == REJECTED) {
onRejected(this.reason)
} else if (this.state == PENDING) {
this.resolveCallback.push(() => {
onFulfilled(this.value)
})
this.rejectCallback.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promise
链式调用:连续打点调用then方法
解决回调地狱问题:函数里面套函数
流程:
-
上一次then的某个回调函数假如返回值是一个promise实例,该promise实例的状态决定调用下一次then的成功还是失败回调 该promise实例的状态是成功态,直接调用下一次then的成功回调。该promise实例的状态是失败态,直接调用下一次then的失败回调
-
上一次then的某个回调函数是普通值,会执行下一次的成功回调
- 上一次then的某个回调函数抛异常 会执行下次then的失败回调
promise链式调用原理 是返回新的Promise实例
lf promise and x refor to the same object, reject promise with a TypeError as the reason.
null也是一个对象 typeof null=>'object'
If onFulfilled is not a function, it must be ignored.
If onRejected is not a function, it must be ignored.
同一个 promise 可以注册多个 then 方法,当 promise 完成或者失败后,对应的 then 方法按照注册顺序依次执行。
检测promise是否符合规范
npm install promises-aplus-tests -g
promises-aplus-tests [文件名Promise-core.js]
promise完整实现
let PENDING = 'PENDING'
let FULFILLED = 'FULFILLED'
let REJECTED = 'REJECTED'
function resolvePromise(promise2, x, resolve, reject) {
// 返回值不一定是一个简单类型,所以拆分出来单独处理
if (promise2 === x) {
return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
}
// 相当于锁的概念
let called;
// 如果是一个函数或者对象 排除null也是个对象 判断是不是一个promise
// 如果是一个promise实例 那么肯定会有then方法,把这个then取出来
if (typeof x === 'object' && x !== null || typeof x === 'function') {
try {
let then = x.then//可能会抛错
if (typeof then === 'function') {//x是一个promise实例
then.call(x, y => {
if (called) return;
called = true;
// 对y进行解析可能y也是一个promise
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true
// resolvePromise(promise2, y, resolve, reject);
reject(r)
})
} else {//x是一个普通对象
resolve(x)
}
} catch (e) {
if (called) return;
called = true;
reject(e)
}
} else {
resolve(x)
}
}
class Promise {
constructor(executor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this.resolveCallback = []
this.rejectCallback = []
let resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.value = value
this.resolveCallback.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED
this.reason = reason
this.rejectCallback.forEach(fn => fn())
}
}
try {
// 执行函数有可能抛错
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 如果是函数继续下一步,不是函数,构造一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err;
}
let p2 = new Promise((resolve, reject) => {
if (this.state === FULFILLED) {
// 放成定时器,异步,确保能够取到p2
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
} else if (this.state == REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
} else if (this.state == PENDING) {
this.resolveCallback.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
this.rejectCallback.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
});
})
}
})
return p2;
}
catch(callback) {
return this.then(null, callback);
}
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise
promise常用api
catch finally 、 all race、 resolve reject
关于catch:
catch()
也会返回一个Promise
,且由于这个Promise
没有返回值,所以打印出来的是undefined
。
返回任意一个非 promise
的值都会被包裹成 promise
对象,因此这里的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
当然如果你抛出一个错误的话,可以用下面👇两种任意一种:
return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')
.then
或者 .catch
的参数期望是函数,传入非函数则会发生值透传。
第一个then
和第二个then
中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1)
的值直接传到最后一个then
里。
它进入的是then()
中的第二个参数里面,而如果把第二个参数去掉,就进入了catch()
中
关于finally:
-
.finally()
方法不管Promise
对象最后的状态如何都会执行 -
.finally()
方法的回调函数不接受任何的参数,也就是说你在.finally()
函数中是没法知道Promise
最终的状态是resolved
还是rejected
的 -
它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的
Promise
对象。
then执行完,不会先将.finally
加入微任务列表,那是因为.then
本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
链式调用后面的内容需要等前一个调用执行完才会执行。
就像是这里的finally()
会等promise1().then()
执行完才会将finally()
加入微任务队列
就算finally2
返回了新的值,它后面的then()
函数接收到的结果却还是'2'
关于all:
.all()
的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
如果一组异步操作中有一个异常都不会进入.then()
的第一个回调函数参数中
.catch
是会捕获最先的那个异常
关于race
.race()
的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
使用.race()
方法,它只会获取最先执行完成的那个结果,其它的异步任务虽然也会继续进行下去,不过race
已经不管那些任务的结果了。
总结:
Promise.all()
的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
.race()
的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
Promise.all().then()
结果中数组的顺序和Promise.all()
接收到的数组顺序一致。
all和race
传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then
的第二个参数或者后面的catch
捕获;但并不会影响数组中其它的异步任务的执行。
new Promise()
并不会阻塞后面的同步代码async1 end
的执行
总结源码思路:
promise有三个状态,分别是:
- pending: 初始状态,不是成功或失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
promise是通过构造函数实例化一个对象的。在promise类的构造函数中接收一个executor执行器函数作为参数,executor立即执行。executor函数有resolve和reject两个参数,这两个参数也是函数,resolve调用之后将初始的pending状态改变为fulfilled状态,reject调用后pending改为rejected状态。
在原型上定义then函数,接收两个参数onFulfilled, onRejected,如果接收的这两个参数不是函数就先对其进行包装。如果当前state的状态为fulfilled就调用onFulFilled函数,如果当前状态是rejected就调用onRejected函数。如果因为异步当前的state状态还没有确定,就先把它们分别加入到队列数组中,直到异步函数执行完毕状态确认后再从数组中拿出来执行。(使用发布/订阅模式)
【只有当 resolve/reject 函数执行后,对应 onFulfilled/onRejected 才可以执行执行,但由于存在异步调用,resolve/reject 执行晚于 then 函数。因此 onFulfilled/onRejected 就可以理解为订阅者,订阅 resolve/reject 函数执行;resolve/reject 是发布者;Promise 提供事件通道作用,存储订阅的 onFulfilled/onRejected 。由于同一个 promise 对象可以注册多个 then 回调,因此 Event Channel 存储回调应为数组格式
因此我们需要修改 resolve/reject 函数的实现,当两者被调用时,同时通知对应订阅者执行。】
由于promise支持链式调用,所以调用then函数的返回值又是一个新的promise。上一个then中的 onFulfilled/onRejected函数返回值作为下一个then中onFulfilled/onRejected函数的参数(作为 promise2 resolve 的参数值)。但是并不是所有类型的返回值 x 都直接返回,因此将对返回值 x
多种情况的处理的这部分逻辑抽离出来成 resolvePromise
方法。
resolvePromise(promise2, x, resolve, reject);
promise2
就是 then
方法返回值,x
是 then
回调函数的返回值,我们根据返回值 x
的情况,决定调用 resolve
或 reject
。
返回值 x
会有那些情况:
x
是个普通值(原始类型)x
是基于Promises/A+
规范promise
对象:判断 x 是否为对象或函数;然后判断 x.then 是否为函数x
是基于其他规范的promise
对象x
就是promise2
(x 与 promise2 指向同一对象)
-
如果
promise
和x
引用的同一对象,则以TypeError
理由拒绝 (避免循环引用) -
如果 x是一个普通对象,resolve(x)
-
如果x是一个promise实例,如果x是一个 promise 就用他的状态来决定 走成功还是失败,一旦失败了 就不在解析失败的结果了,直接reject(x)
-
如果
x
既不是对象也不是函数,使用x
作为值完成promise
最后,添加锁,避免成功后执行失败,避免失败后执行成功
generator迭代器
function* read() {
let a = yield 'node';
console.log(a);//hi
let b = yield 'vue';
return 100;
}
let it = read()
console.log(it.next('abc'))//{ value: 'node', done: false }
console.log(it.next('hi'))//{ value: 'vue', done: false }
console.log(it.next('hello'))//{ value: 100, done: true }
//next中传的参数是上一个yield的返回值
可以用来解决异步流程问题
let fs = require('fs').promises;
function* read() {
let filename = yield fs.readFile('D:\\TUNG\\JavaScript\\ES\\es6\\12generator\\a.txt', 'utf8');
let bContent = yield fs.readFile(filename, 'utf8');
return bContent;
}
let it = read();
//{ value: Promise { <pending> }, done: false }
let { value, done } = it.next();
// data就是a.txt 中的内容
value.then((data) => {
let { value, done } = it.next(data)
value.then((data) => {
let { value, done } = it.next(data);
console.log(value, done);
})
})
使用现成的库函数
npm init -y
npm i co
let fs = require('fs').promises;
let co=require('co')
function* read() {
let filename = yield fs.readFile('D:\\TUNG\\JavaScript\\ES\\es6\\12generator\\a.txt', 'utf8');
let bContent = yield fs.readFile(filename, 'utf8');
return bContent;
}
co(read()).then(data => {
console.log(data);
})
实现
let fs = require('fs').promises;
function* read() {
let filename = yield fs.readFile('D:\\TUNG\\JavaScript\\ES\\es6\\12generator\\a.txt', 'utf8');
let bContent = yield fs.readFile(filename, 'utf8');
return bContent;
}
function co(it) {
return new Promise((resolve, reject) => {
function next(data) {
let { value, done } = it.next(data)
if (done) {
resolve(value)
} else {
Promise.resolve(value).then(data => {
next(data);
}), err => {
it.throw(err);
}
}
}
next()
})
}
co(read()).then(data => {
console.log(data);
})
async+await
let fs = require('fs').promises;
async function read() {
let filename = await fs.readFile('D:\\TUNG\\JavaScript\\ES\\es6\\12generator\\a.txt', 'utf8');
let bContent = await fs.readFile(filename, 'utf8');
return bContent;
}
read().then((data) => {
console.log(data);
})
函数前加async关键字就变成了async函数
1.async函数的返回值是一个promise实例,promise实例的结果由async函数的返回值决定
-
返回值如果是一个非promise实例或没有返回值相当于返回undefined,async函数返回值是一个成功态的promise实例
-
如果返回值是一个promise实例,async函数返回值promise实例与该返回值的promise实例保持一致
2.await
-
await必须在async函数中,一般都配合一起使用
-
await表达式可以取到promise实例的结果
-
await后侧一般都是Promise实例,也可以是非promise类型
如果在async函数中抛出了错误,则终止错误结果,不会继续向下执行。
如果想要使得错误的地方不影响async
函数后续的执行的话,可以使用try catch
或者你可以直接在Promise.reject
后面跟着一个catch()
方法
async
函数中await
的new Promise
要是没有返回值的话则不执行后面的内容 promise后面的代码和then都不执行
本质就是用一个同步函数的语法糖来表示异步函数
async function async1() {
console.log('A');
await async2()
console.log('B');
}
async function async2() {
console.log('C');
}
console.log('D');
将async转换为promise
function async1() {
console.log("A")
new Promise((resolve) => {
console.log("C");
resolve()
}).then((res) => console.log("B"))
}
console.log("D")
async处理异常
📌总结ES6的新特性
ES6 在变量的声明和定义方面增加了 let、const 声明变量,有局部变量的概念,
赋值中有结构赋值,
同时 ES6 对字符串、 数组、正则、对象、函数等拓展了 一些方法,如字符串方面的模板字符串、函数方面的默认参数、对象方面属性的简洁 表达方式,
ES6 也 引入了新的数据类型 symbol,可以通过 typeof 检测出来,新的数据结构 set 和 map,
为解决异步回调问题,引入了 promise 和 generator,
实现 Class 和模块,通过 Class 可以更好的面向对象编程,使用模块加 载方便模块化编程,当然考虑到 浏览器兼容性,我们在实际开发中需要使用 babel 进 行编译
重要的特性:
块级作用域:ES5 只有全局作用域和函数作用域,块级作用域的好处是不再需要立即执 行的函数表达式,循环体中的闭包不再有问题
rest 参数:用于获取函数的多余参数,这样就不需要使用 arguments 对象了,
promise:一种异步编程的解决方案,比传统的解决方案回调函数和事件更合理强大
模块化:其模块功能主要有两个命令构成,export 和 import,export 命令用于规定模 块的对外接口,import 命令用于输入其他模块提供的功能
六、Eventloop事件循环(事件轮询)
同步异步流程
-
JavaScript
将任务分为同步任务和异步任务,同步任务进入主线(call stack)中,异步任务首先到Event Table
进行回调函数注册。 -
当异步任务的触发条件满足,将回调函数从
Event Table
压入Event Queue
中。 -
主线程里面的同步任务执行完毕,系统会去
Event Queue
中读取异步的回调函数。 -
只要主线程空了,就会去
Event Queue
读取回调函数,这个过程被称为Event Loop
。
常见异步任务
DOM
事件AJAX
请求- 定时器
setTimeout
和setlnterval
ES6
的Promise
【Promise定义部分为同步任务,回调部分为异步任务】
宏任务和微任务
JavaScript
除了广义上将任务划分为同步任务和异步任务,还对异步任务进行了更精细的划分。异步任务又进一步分为微任务和宏任务。
history traversal
任务(h5
当中的历史操作)process.nextTick
(nodejs
中的一个异步操作)MutationObserver
(h5
里面增加的,用来监听DOM
节点变化的)
宏任务和微任务分别有各自的任务队列Event Queue
,即宏任务队列和微任务队列。
微任务执行时机比宏任务早,async+await是微任务
宏任务:setTimeout、setInterval、Ajax、DOM事件
微任务:Promise async/await
script(主程序代码)——>process.nextTick——>promise——>setTimeout
Event Loop执行过程
代码开始执行,创建一个全局调用栈,script
作为宏任务执行
执行过程同步任务立即执行,异步任务根据异步任务类型分别注册到微任务队列和宏任务队列
同步任务执行完毕,查看微任务队列
- 若存在微任务,将微任务队列全部执行(包括执行微任务过程中产生的新微任务)
- 若无微任务,查看宏任务队列,执行第一个宏任务,宏任务执行完毕,查看微任务队列,重复上述操作,直至宏任务队列为空
任务队列中,在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一 直提取,直到 microsoft 队列为空为止。
也就是说如果某个 microtask 任务被推入到执行中,那么当主线程任务执行完成后, 会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为 止。而事件循环每次只会入栈一个 macrotask,主线程执行完成该任务后又会检查 microtasks 队列并完成里面的所有任务后再执行 macrotask 的任务。 macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering microtasks: process.nextTick, Promise, MutationObserve
js单线程且和dom渲染共用线程
eventloop和dom渲染
callback空闲->执行微任务->尝试dom渲染->触发eventloop
宏任务dom渲染后完成,微任务dom渲染前完成
链式调用的每一级then都算下一级的微任务队列中
练习题 https://juejin.cn/post/6844904077537574919
七、其他
图片的懒加载和预加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
性能优化
减少 HTTP 请求
使用内容发布网络(CDN)
添加本地缓存
压缩资源文件
将 CSS 样式表放在顶部,把 javascript 放在底部(浏览器的运行机制决定)
避免使用 CSS 表达式
减少 DNS 查询
使用外部 javascript 和 CSS
避免重定向
图片 lazyLoad
JS 的语言特性
运行在客户端浏览器上; 不用预编译,直接解析执行代码; 是弱类型语言,较为灵活; 与操作系统无关,跨平台的语言; 脚本语言、解释性语言
跨域
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏 览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都 被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。
a-zA-Z0-9_- ↩︎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步