# JavaScript
# 关于JS
# JS是一门怎样的语言
JS是一门面向对象、弱类型、跨平台的动态脚本语言。
# V8执行JS的过程
# 生成AST(抽象语法树)
# 词法分析
词法分析即分词,它的工作就是将一行行的代码分解成一个个token。
let name = 'fendy'

# 语法分析
将生成的这些 token 数据,根据一定的语法规则转化为AST。
# 将AST转成字节码
# 执行代码
由解释器逐行执行字节码,遇到热点代码启动编译器进行编译,生成对应的机器码, 以优化执行效率。
# JS引擎解析过程
# JS存储数据的方式
1、简单数据类型以栈的形式存储,存储的是值。
2、复杂数据类型以堆的形式存储,地址(指向堆中的值)存储在栈中。
# 变量

暂、变、作、初、重
# 初始化值
# 重复声明
# 暂时性死区
在块级作用域里面,如果寻找不到变量,它不会去上一级作用域继续寻找作用域了。
var tmp = 123;
if (true) {
tmp = 'abc'; //ReferenceError: Cannot access 'tmp' before initialization
let tmp;
}
# 变量提升
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
let a=1;
console.log(c); // undefined
var c=1
# 作用域
var : 全局、函数
let :全局、函数、块级
const:全局、函数、块级
# 作用域与内存
# 作用域
作用域就是变量和函数的可访问范围。它规定了如何查找变量,也就是当前执行代码对变量的访问权限。
**执行上下文:**可以简单理解为一个对象。它有三种类型,全局执行上下文、函数执行上下文、eval()执行上下文。
**分类:**全局作用域、函数作用域、块级作用域(ES6)
# 作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
**作用:**保证当前执行环境里,有权访问的变量和函数是有序的(作用域链的变量只能向上访问变量,访问到window对象时被终止)
作用域链和原型继承查找的区别: 如果去查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出ReferenceError
# this
# 箭头函数
- 在箭头函数中,this 引用的是定义箭头函数的上下文。
- 事件回调中,使用箭头函数取代_this=this重复定义上下文的问题。
# 绑定方式
绑定方式优先级:new > 显式绑定 > 隐式绑定> 直接调用
# new绑定
此时构造函数中的this指向实例对象
# 显式绑定
call、apply、bind区别:
1、call()、apply()属于立即执行函数,区别在于接收的参数形式不同,前者是一次传入参数,后者参数可以是数组。
2、bind()则是创建一个新的包装函数,并且返回,它不会立即执行bind(this,arg1,arg2···)。
3、在严格模式下,调用函数时如果没有指定上下文对象,则this值不会指向window。 除非使用 apply()或 call()把函数指定给一个对象,否则 this 的值会变成 undefined。
function foo(){
console.log(this.a);
bar.apply({a:5},arguments)
}
function bar(b){
console.log(this.a + b);
}
var a = 1; // 全局 a 变量
foo(3)
# 隐式绑定
对象调用,指向对象
function foo(){
console.log(this.a)
}
var obj = {
a = 1,
foo
}
obj.foo() // 1 → this 指向 obj
# 默认绑定
全局调用,this指向window
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2 → this指向全局
# DOM事件绑定
元素监听事件addEventerListener中 this 默认指向绑定事件的元素。
# 箭头函数
箭头函数不能使用 arguments、super 和 new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。
- 没有arguments
- 没有prototype
- 没有super
- 没有new.target
- 没有属于自己的this
- 不能作为构造函数
# 原型&原型链
# 原型
原型是一个简单的对象,用于实现对象的属性继承。 每一个JavaScript对象(null除外)在创建的时候就会有一个与之关联的对象,这个对象就是原型对象,每一个对象都会从原型上继承属性。
# 构造函数
可以通过new来创建一个对象的函数。
# 实例
通过构造函数和new创建出来的对象。
# 三者关系

# 获取原型方法
假设person是一个实例
- person.constructor.prototype
- person._ proto __
- Object.getPrototypeOf(person) = person._ proto __
# 原型链
JavaScript对象通过prototype指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链。
# 原型链机制
属性查找机制
当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象
Object.prototype(Object.prototype.__proto__ === null),假如还是没有找到,则输出undefined。属性修改机制
只会修改实例对象本身的属性,如果不存在,则进行添加该属性。如果需要修改原型的属性,则需要通过
prototype属性(b.prototype.B = 1),但这样修改会导致所有继承于这个对象的实例的属性发生改变。
# 查找属性方法
- hasOwnProperty() // 不查找原型上面的属性
- 使用 in 检查对象中是否含有某个属性 // 遍历所有的key,包括原型上的
# 闭包
# 定义
闭包是指有权访问另外一个函数作用域中变量的函数。 简单来说,闭包就是一个函数A内部有另一个函数B,函数B可以访问到函数A的变量和方法,此时函数B就是闭包。
# 产生原因
当前环境中存在指向父级作用域的引用。
# 特征
- 闭包可以更新外部变量的值。
- 内部函数可以引用外层的参数和变量。
- 参数和变量不会被垃圾回收制回收。
# 优点
- 能够封装对象的私有属性和私有方法。
- 可以读取函数内部的变量,并且让这些变量始终保持在内存中。
# 缺点
闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
解决方法:在退出函数之前,将不使用的局部变量全部删除
# 具体应用场景
# 函数防抖节流
# 使用闭包设计单例模式
class CreateUser {
constructor (name) {
this.name = name
this.getName()
}
getName () {
return this.name
}
}
// 代理实现单例模式
const ProxyMode = (function () {
let instance = null
return function (name) {
if (!instance) {
instance = new CreateUser(name)
}
return instance
}
})()
# 封装使用变量或者私有属性
# 经典问题
for(var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//结果为 3 3 3
//利用IIFE(立即执行函数表达式)构建闭包解决var没有块级作用域的问题
for(var i = 1;i < 3; i++){
(function (i) {
setTimeout(()=>{
console.log(i);
})
})(i)
}
//结果为 0 1 2
for(let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 结果为 0 1 2
// 每个 let 和代码块结合起来形成块级作用域,当 setTimeout() 打印时,会寻找最近的块级作用域。
//给定时器传入第三个参数, 作为timer函数的第一个函数参数
for (var i = 0; i < 5; i++) {
setTimeout(function (j) {
console.log(j);
}, 0, i);
}
# 数据类型
7种基本数据类型1种引用数据类型
# 基本数据类型
Number、Null、Undefined、String、Boolean、Symbol、BigInt
(NNUSB)
BigInt: BigInt是一种新的数据类型,用于当数值大于Number数据类型的支持。
# 引用数据类型
Object,Array和Function属于Object
# Object
# Array
# Function
# 数据类型转换
# 数字取舍
# 四舍五入
Math.round(2.33) // 3
# 向上取整
- Math.ceil(2.33) // 3
- 2.33|1
# 向下取整
- Math.floor(2.33);
- 2.33|0
# ==的转换规则
- 两边的类型是否相同,相同的话就比较值的大小,例如1==2,返回false
- 两边如果是null和undefined,就返回true。null、undefined和其他任何值比较均为false
- 两边是String和Number,String转成Number
- NaN和任何相比都返回false(包括自己)
- 一边为Object另一边为String、Number、Symbol,Object会转成字符串
# 对象转原始类型流程
- 如果Symbol.toPrimitive()方法,优先调用再返回。
- 调用valueOf(),如果转换为原始类型,则返回。//valueOf 返回 String 对象的原始值
- 调用toString(),如果转换为原始类型,则返回
- 如果都没有返回原始类型,会报错。
let person={ name:'Fendy',age: 16 }
person.toString() // '[object Object]'
person.toString=()=>{ return 'toString' }
person.toString() // 'toString'
person == 'toString' // true
person.valueOf=()=>{ return 'valueOf' }
person == 'toString' // false
person == 'valueOf' // true
# 加法运算
当运算符其中一方为字符串时,那么另一方也转换为字符串
# 进制转换
# 十进制转其它进制
(10).toString(2) // 十进制二进制
# 其它进制转十进制
parseInt('1010',2) // 10
# 判断数据类型
# typeof
- 能够正确判断简单数据类型(原始类型),除了null,typeof null结果为object。
- typeof不能正确判断对象类型,typeof仅可以区分开function,除此之外,结果均为object。
之所以typeof null 会输出 object,这是JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object 。
# instanceof
# 作用
检测某个构造函数的原型对象在不在某个对象的原型链上,能够准确判断复杂数据类型,但是不能正确判断简单数据类型。
# 原理
instanceof是通过原型链进行判断的,A instanceof B,在A的原型链中层层查找,查找是否有原型等于B.prototype,如果一直找到A的原型链的顶端,即Object.prototype.__proto__,仍然不等于B.prototype,那么返回false,否则返回true
# 手写instanceof
function myInstanceof(left,right) {
// 如果是基本数据类型,直接返回false
if (left==null || typeof left!=='object') return false;
//getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto==null) return false;
if (proto===right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
console.log(myInstanceof(111, Number)); //false
console.log(myInstanceof(new Number(111), Number)); //true
# Object.prototype.toString.call
//判断是否为数组
Object.prototype.toString.call(arr) === "[object Array]"
// 判断对象
Object.prototype.toString.call(person) === '[object Object]'
# Array
# 判断数组
- Array.isArray(obj ) // ES6 api
- obj instanceof Array // 原型链查找
- obj.constructor === Array // 构造函数类型判断
- Object.prototype.toString.call(arr) === "[object Array]" toString 返回表示该对象的字符串,若这个方法没有被覆盖,那么默认返回 "[object type]" ,其中 type 是对象的类型。需要准确判断类型的话,建议使用这种方法。
# 类数组转数组
- Array.from(arr)
- [...arr]
# 相关方法
/*1、map
作用: 遍历数组,返回回调返回值组成的新数组。
参数:(value, index, array) => {}
返回值:新数组。
*/
let arr = [1, 2, 3, 4, 5];
let b = arr.map((i) => {
return i + 2;
});
console.log(b)//b:[3,4,5,6,7]
/*2、forEach
作用: 调用数组的每个元素,并将元素传递给回调函数。
参数:(item,index,arr)=>{}
返回值:undefined。
forEach无法break,可以用try/catch中throw new Error来停止。
forEach() 对于空数组是不会执行回调函数的。
*/
let arr = [1, 2, 3, 4, 5, 5, 1];
arr.forEach((value, index, array) => {
array[index] = value * 5;
});
console.log(arr); //[5, 10, 15, 20, 25, 25, 5]
/*3、filter
作用:检测数值元素,并返回符合条件所有元素的数组。
参数:(value) => {}
返回值:新数组
filter() 不会对空数组进行检测。
filter() 不会改变原始数组。
*/
let arr = [1, 2, 3, 4, 5, 5, 1];
let b = arr.filter((value) => value > 3);
console.log(b); // [4, 5, 5]
/*4、some
作用:方法用于检测数组中的元素是否满足指定条件。有一个元素满足条件,则表达式返回true
参数:(value, index, array)=>{}
返回值:boolean
some() 不会对空数组进行检测。
*/
let arr = [1, 2, 3, 4, 5, 5, 1];
let b = arr.some((value, index, array) => value > 6);
console.log(b); // false
/*5、every
作用:检测每个元素是否都符合条件。有一个元素不满足,则表达式返回false
参数:(value, index, array)=>{}
返回值:boolean
every() 不会对空数组进行检测。
*/
let arr = [1, 2, 3, 4, 5, 5, 1];
let b = arr.every((value, index, array) => value > 0);
console.log(b); // true
/*6、join
作用:把数组中的所有元素转换一个字符串。
参数:separator。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。
返回值:string
*/
let arr = [1, 2, 3, 4, 5, 5, 1];
let b = arr.join("_");
console.log(b); // 1_2_3_4_5_5_1
/*7、concat
作用:连接两个或多个数组。
参数:array2,array3,...,arrayX
返回值:新数组
*/
let [animal,fruit,country] =[ ['cat', 'dog'],['apple', 'pear'], ['China', 'USA']];
let mix = animal.concat(fruit, country);
console.log(mix); // ["cat", "dog", "apple", "pear", "China", "USA"]
//合并数组,改变数组arr,返回数组长度。
[].push.allay(arr,arr1)
/*8、push/unshift
作用:在尾部或头部添加元素
参数:item1, item2, ..., itemX
返回值:number,新数组长度
改变原数组。
*/
let animal = ['cat', 'dog'];
let animal2 = animal.push('bird');
console.log(animal); // ["cat", "dog", "bird"]
console.log(animal2); // 3
/*9、pop/shift
作用:删除数组最后一个或第一个元素
参数:无
返回值:返回被删除的元素
改变原数组。
*/
let animal = ['cat', 'dog'];
let animal2 = animal.shift();
console.log(animal); // ["dog"]
console.log(animal2); // cat
/*10、slice
作用:截取选定区域的数组元素。
参数:start, end 0指第一个元素,-1指最后一个元素。
返回值:截取元素组成的新数组
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let res = animal.slice(0, 2);
console.log(animal); // ["cat", "dog", "bird", "duck", "pig"]
console.log(res); // ["cat", "dog"]
/*11、splice
作用:添加或删除数组中的元素。
参数:index,howmany,item1,.....,itemX index:添加/删除元素的索引。howmany:删除多少元素,默认删除index到最后。item:添加的新元素。
返回值:删除元素组成的新数组
改变原数组。
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let res = animal.splice(-2, 2, 'fox');
console.log(animal); // ["cat", "dog", "bird", "fox"]
console.log(res); // ["duck", "pig"]
//指定位置插入指定元素
function insert(arr, item, index) {
return arr.slice(0,index).concat(item,arr.slice(index));
}
/*12、indexOf/lastIndexOf
作用:查找元素第一次/最后一次出现的位置
参数:item,start 如果找到一个item,则返回item的第一次出现的位置,没找到指定元素则返回-1。
返回值:number
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let res = animal.indexOf('bird');
console.log(res); // 2
/*13、reduce/reduceRight
作用:接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduceRight从末尾到开头累加。
参数:((previousValue, currentValue, currentIndex, array) => {})
返回值:累加的结果
对于空数组是不会执行回调函数的,只有初始值直接返回初始值。
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let res = animal.reduce(((previousValue, currentValue, currentIndex, array) => previousValue +"-"+ currentValue));
console.log(res); // cat-dog-bird-duck-pig
let sort = [10, 20, 30, 40, 50];
let res = sort.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
console.log(res); // 150
/*14、sort
作用:对数组里的字符串或数字进行排序。
参数:(a, b) => {}。默认按字母类型升序,
返回值:排序后的数组
改变原始数组
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let numbers = [66, 20, 9, 7, 50];
console.log(numbers.sort()); // [20, 50, 66, 7, 9]
console.log(animal.sort()); // ["bird", "cat", "dog", "duck", "pig"]
console.log(numbers.sort(((a, b) => a - b)));// [7, 9, 20, 50, 66]
console.log(animal.sort((a, b) => a - b));// ["bird", "cat", "dog", "duck", "pig"]
console.log(numbers.sort(((a, b) => b - a))); // [66, 50, 20, 9, 7]
console.log(animal.sort((a, b) => b - a)); // ["bird", "cat", "dog", "duck", "pig"]
/*15、reverse
作用:反转数组
参数:无
返回值:反转后的新数组
改变原始数组
*/
let animal = ['cat', 'dog', 'bird', 'duck', 'pig'];
let res = animal.reverse();
console.log(res); // [ 'pig', 'duck', 'bird', 'dog', 'cat' ]
/*16、flat()
作用:扁平化数组
参数:无
返回值:新数组
*/
let arr = [1, 2, [3, 4], 5, 6];
let res=arr.flat()
console.log(res); // [1, 2, 3, 4, 5, 6]
/*16、find()
作用:返回数组中满足条件的第一个元素的值,如果没有,返回undefined
参数:
返回值:返回数组中满足条件的第一个元素的值
*/
let arr=[1,2,3,4];
let result = arr.find(item =>{
return item > 3
});
console.log(result); //4
/*17、findeIndex()
作用:返回数组中满足条件的第一个元素的下标,如果没有返回-1
参数:
返回值:返回数组中满足条件的第一个元素的下标
*/
let arr=[1,2,3,4];
let result = arr.findeIndex(item =>{
return item > 3
});
console.log(result); //3
# Object
# 相关方法
# Object.keys()
# Object.values()
# Object.entries()
# Object.fromEntries()
# String对象
# 属性
# 方法
# charAt
/*
作用:返回指定位置的字符
参数:无
返回值:字符
*/
let str = 'IamFendy';
let ans = str.charAt(3);
console.log(ans); // F
# indexOf/lastIndexOf
/*
作用:查找字符串首次出现的位置/从后向前搜索字符串,并从起始位置(0)开始计算返回字符串最后出现的位置。
参数:字符串
返回值:number
注意:1、找不到就返回-1
2、区分大小写
*/
let str = 'I am fendy,';
let ans = str.indexOf('fendy');
console.log(ans); // 5
# includes
/*
作用:判断字符串是否包含指定的子字符串
参数:字符串
返回值:boolean
*/
let str = 'I am fendy,';
let ans = str.includes('fendy');
console.log(ans); // true
# search
/*
作用:查找相匹配的字符串
参数:(searchvalue)
返回值:首次匹配到的起始位置,找不到返回-1
*/
let str="I love Fendy";
let ans=str.search('Fendy');
console.log(ans); // 7
# match
/*
作用:在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
参数:(regexp)
返回值:存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。 如果没找到匹配结果返回 null 。
*/
let str="The rain in SPAIN stays mainly in the plain";
let ans=str.match(/ai/ig);
console.log(ans); // ["ai", "AI", "ai", "ai"]
# concat
/*
作用:拼接字符串
参数:字符串
返回值:拼接后的字符串
*/
let str = 'I am fendy,';
let str1 = 'str1',str2='str2';
let ans = str.concat(str1,str2);
console.log(ans); // I am fendy,str1str2str2
# repeat
/*
作用:字符串复制指定次数
参数:(count)
返回值:返回复制指定次数并连接在一起的字符串。
*/
let str="I love Fendy";
let ans=str.repeat(2);
console.log(ans); // 'I love FendyI love Fendy'
# slice/substring
/*
作用:截取字符串
参数:start(包含) 和 end(不包含)
返回值:被截取的字符串
注意:-1 指字符串的最后一个字符,-2 指倒数第二个字符 (substring()不支持从后面截取)
*/
let str = 'I am fendy,';
let ans = str.slice(2, 4);
console.log(ans); // am
# substr
/*
作用:截取指定数目的字符
参数:start(包含) 和 n(长度)
返回值:被截取的字符串
*/
let str = 'I am fendy,';
let ans = str.substr(7, 5);
console.log(ans); // fendy
# split
/*
作用:把一个字符串分割成字符串数组
参数:(字符串或正则表达式,返回的数组的最大长度)
返回值:新数组
注意:如果不设置参数就会将每个字符分割
*/
let str="How are you doing today?";
let ans=str.split(" ");
console.log(ans); // [How,are,you,doing,today?]
# replace
/*8、replace()
作用:用一些字符串取代另一些字符串
参数:(searchvalue,newvalue) searchvalue:字符串或者正则表达式
返回值:新字符串
*/
let str="I love Fendy";
let ans=str.replace('Fendy','sin');
console.log(ans); // I love sin
# toUpperCase/toLowerCase
/*
作用:把字符串转成大写/小写
参数:无
返回值:新字符串
*/
let str="How are you doing today?";
let ans=str.toUpperCase();
console.log(ans); // HOW ARE YOU DOING TODAY?
# 包装类型
Boolean、Number、String这三种原始类型可把原始类型的值包装成对象,比如在字符串调用函数时,引擎会将原始类型的值转换成只读的包装对象,执行完函数后就销毁。
装箱: 即原始类型转换为包装类型。
拆箱: 即包装类型转换为原始类型。
let name = new Number('123');
console.log(typeof name); //object
console.log(typeof name.toString()); //string
console.log(typeof name.valueOf()); //number
**valueOf : ** 返回最适合该对象类型的原始值。
**toString : ** 将该对象的原始值以字符串形式返回。
在数值运算里,会优先调用valueOf(),如a + b; 在字符串运算里,会优先调用toString(),如alert(c)。
// Array
let arr=[3,5,[1, 2, [3, 4], 5, 6],5]
arr.valueOf() // [ 3, 5, [ 1, 2, [ 3, 4 ], 5, 6 ], 5 ]
arr.toString() // '3,5,1,2,3,4,5,6,5'
// Object
let person={ name:'Fendy',age: 16 }
person.toString() // '[object Object]'
person.valueOf() // { name: 'Fendy', age: 16 }
相关题目
//让 a == 1 && a == 2 && a == 3 为 true
const a = {
value:[3,2,1],
valueOf:function(){
return this.value.pop()
} // 每次调用,删除一个元素
}
console.log(a == 1 && a == 2 && a == 3) // true (注意仅能判断一次)
# 运算符
# 逻辑位运算
# 位与(&)
每个比特位都为1,则结果为1,否则为0
1 0 1 // 5
1 0 0 // 4
1 0 0 // 4
# 位或(|)
每个比特位都为0,则结果为0,否则为1
1 0 0 // 4
1 0 1 // 5
1 0 1 // 5
# 位异或(^)
每个比特位相同为0,相异为1
1 0 0 // 4
1 0 1 // 5
0 0 1 // 1
# 按非位(~)
对每一个比特位执行非(NOT)操作。NOT a 结果为 a 的反转(即反码)。
const a = 5; // 00000000000000000000000000000101
console.log(~a); // 11111111111111111111111111111010
# 移位运算
# 左移(<<)
1001 // 9
9 << 2 // 100100
9*2^2
# 有符号右移(>>)
1001 // 9
9 >> 2 // 2
# 无符号右移(>>>)
# 展开运算符
# 某些场景中可以替代apply
let arr = [1, 2, 3, 4, 5];
console.log(Math.max.apply(null, arr)); //ES5
console.log(Math.max(...arr)); //ES6
# 代替数组的push、concat等方法
let arr = [1, 2, 3, 4, 5];
let arr1 = [6, 7, 8, 9];
//将一个数组追加到另外一个数组中
Array.prototype.push.apply(arr, arr1); //ES5
arr.push(...arr1) //ES6
//arr为[1, 2, 3, 4, 5 , 6, 7, 8, 9]
# 浅拷贝数组或对象
let arr = [1, 2, 7, 9, 5];
let copyArr = [...arr]; //copyArr: [1, 2, 7, 9, 5]
# 将伪数组转化为数组
let nodeList = document.querySelectorAll('div');
// querySelectorAll 方法返回的是一个 nodeList 对象。它不是数组,而是一个类似数组的对象。
console.log([...nodeList]) // [div,div,div,...]
# 剩余运算符
# 作为参数使用
rest参数可理解为剩余的参数,所以必须在最后一位定义
function add(a, ...b) {
//do sth.
}
# 与解构赋值组合使用
let array = [1,2,3,4,5,6]
let [a,b,..c]=array; //c:[3,4,5,6]
# 继承
# 继承方式
# 盗用构造函数继承
function Person(name) {
this.name = name;
}
function Girl() {
// 继承 Person
Person.call(this, 'Fendy');
// 或者
// Person.apply(this, 'Fendy)
}
let girl = new Girl();
console.log(girl.name); // Fendy
优点:
- 可以传参
- 避免属性被所有实例共享
**缺点:**无法获取父元素的方法。
# 原型链继承
function Person() {
this.name = 'name';
function say() {
}
}
Person.prototype.say = function () {
console.log("Hi,Iam~")
};
function Girl() {
}
// 继承 Person
Girl.prototype = new Person();
let girl = new Girl();
console.log(girl.say()); // Hi,Iam~
**优点:**可以获取父类的方法。
缺点:
- 引用类型的属性被所有实例共享
- 不可传递参数
# 组合继承
function Person(name) {
this.name = name;
this.habits = ['swimming', 'basketball'];
}
Person.prototype.say = function () {
console.log('Iam'+this.name)
};
function Girl() {
// 继承属性
Person.call(this, 'Fendy');
}
// 继承方法
Girl.prototype = new Person();
let girl = new Girl();
let girl2 = new Girl();
girl2.habits.push('eat');
console.log(girl2.habits); // ["swimming", "basketball", "eat"]
console.log(girl.habits); // ["swimming", "basketball"]
# 原型式继承
模拟ES5中Object.create的实现,将传入对象作为创建对象的原型。本质上,object()是对传入的对象执行了一次浅复制。
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
// 规范化
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
# 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) {
let clone = Object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
# 组合寄生继承
- 创建父类原型副本
- 为创建的副本增加constructor属性,从而弥补了因为重写原型而失去的默认的constructor属性。
- 将创建的对象赋值给子类型的原型。
function inheritPrototype(subType,superType){
var prototype = Object(superType.prototype);//创建对象
prototype.constructor = subType;//增强对象
subType.prototype = prototype;//指定对象
}
# 继承缺点
无法决定继承哪些属性,所有属性都得继承。
# 解决方案
用组合的方式,先设计一系列属性或者方法,然后将这些属性方法进行拼装,来形成不同的实例或者类。
function drive(){
console.log("wuwuwu!");
}
function music(){
console.log("lalala!")
}
function addOil(){
console.log("哦哟!")
}
let car = compose(drive, music, addOil);
let newEnergyCar = compose(drive, music);
# 内置对象
# history
| back() (opens new window) | 加载 history 列表中的前一个 URL |
|---|---|
| forward() (opens new window) | 加载 history 列表中的下一个 URL |
| go() (opens new window) | 加载 history 列表中的某个具体页面 |
H5新方法:
# pushState
# replaceState
# popstate
# Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
# 属性
- constructor //构造函数
- size //长度
# 操作方法
- add //添加某个值,返回 Set 结构本身
- delete //删除某个值,返回一个布尔值
- has //返回一个布尔值,表示该值是否为Set的成员
- clear //清除所有成员,没有返回值
# 遍历方法
- keys //返回键名的遍历器
- values //返回键值的遍历器
- entries //返回键值对的遍历器
- forEach //使用回调函数遍历每个成员
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
# WeakSet
# 与Set区别
- WeakSet 的成员只能是对象,而不能是其他类型的值。
- WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。WeakSet 的成员是不适合引用的,因为它会随时消失。
# 方法
weakset只有3个方法,并且没有size属性。所以,不能遍历它。
- add
- delete
- has
# 作用
WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
# Map
Map是ES6新增的 数据结构,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。key是跟内存地址绑定的。
# 属性
- size
- constructor
# 操作方法
- set(key,value)
- get(key) //如果找不到key,就返回undefined。
- has(key)
- delete(key)
- clear()
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
items.forEach(
([key, value]) => map.set(key, value)
);
# 遍历方法
- keys
- values
- entries
- forEach
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
map.forEach(function(value, key, map) {
console.log(key + "-" +value );
});
// name-张三
// title-Author
# WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
- WeakMap的键名所指向的对象,不计入垃圾回收机制。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存,不用手动删除。
# JS事件
# 事件捕获
事件从最不精准的目标(document对象)开始触发,然后到最精确的目标 (不精确 → 精确)
# 事件冒泡
事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发 (特定 → 不特定)
不支持冒泡的事件:
- 鼠标事件:mouseleave、mouseenter
- 焦点事件:blur、focus
- UI事件:scroll、resize
事件先冒泡后捕获: 对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件时,先暂停执行,直到冒泡事件被捕获后再执行事件。
# 事件委托
事件委托,就是利用事件冒泡的原理,把自己所触发的事件交给父元素代替执行。即:不在事件(直接DOM)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生在哪一个子元素上来做出不同的响应。
好处:
- 提高性能
- 新添加的元素也能触发绑定在父元素上的监听事件
# 事件循环机制
# 为什么JS是单线程
JS作为主要运行在浏览器的脚本语言,主要用途之一就是操作DOM。
如果JS同时有两个线程,同时对同一个DOM进行操作,这时浏览器不知道应该听谁。为了避免这种问题,所以就把JS设计成单线程语言。
# 执行栈
当执行某个函数、事件(指定过回调函数)时,就会进入执行栈中,等待主线程读取。
# 主线程
js 引擎执行的线程,主要负责页面渲染、函数处理。
主线程循环: 即主线程会不停的从执行栈中获取事件,然后执行完所有栈中的同步代码。
任务队列: 当遇到一个异步事件后,主线程会将这个事件挂在任务队列中。当主线程将执行栈中的所有代码都执行完后,主线程将会去查看任务队列中是否存在任务。 如果存在,那么主线程会依次执行那些任务队列中的回调函数。
异步运行机制:
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个任务队列。只要异步任务有了返回结果,就在任务队列之中放置一个事件。
- 当执行栈中的所有同步任务执行完毕,就会去查看任务队列,那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
- 主线程会重复第三点。
# 宏任务和微任务
宏任务:
script(整体代码),setTimeout,setInterval,requestAnimationFrame(有争议),setImmediate,UI渲染,I/O流操作,postMessage,MessageChannel
微任务:
存储在宏任务队列中,当宏任务执行完了回去检查有没有微任务需要执行。如果没有就直接执行下一个宏任务。
Promise.then/reject,MutaionObserver,process.nextTick、fetchAPI、V8垃圾回收过程
微任务的好处:
- 采用异步回调替代同步回调解决了浪费 CPU 性能的问题。
- 放到当前宏任务最后执行,解决了回调执行的实时性问题。
Eg:
console.log('start');
setTimeout(() => {
console.log('timeout');
});
Promise.resolve().then(() => {
console.log('resolve');
});
console.log('end');
/*
start
end
resolve
timeout
*/
- 刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈。
- setTimeout 作为一个宏任务放入宏任务队列。
- Promise.then作为一个为微任务放入到微任务队列。
- 当本次宏任务执行完,检查微任务队列,发现一个Promise.then,执行。
- 接下来进入到下一个宏任务——setTimeout, 执行。
# 事件循环
一次事件循环只执行处于 Macrotask 队首的任务,执行完成后,立即执行 Microtask 队列中的所有任务。
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
- 一开始整体脚本作为一个宏任务执行。
- 执行过程中,如果遇到宏任务进入宏任务队列,微任务进入微任务队列。
- 当前的宏任务出队,检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列。
- 执行浏览器UI线程的渲染工作。(每一次事件循环,浏览器都可能会去更新渲染)。
- 检查是否有Web Worker
- 重复以上步骤。
# 自定义事件
创建自定义事件
初始化自定义事件
监听自定义事件
触发自定义事件
// 方式一
// let myEvent = new Event('myEvent');
// 方式二
// let myEvent = new CustomEvent('myEvent', {
// detail: {
// name: 'lindaidai'
// }
// })
// 方式三
let myEvent = document.createEvent('CustomEvent');
myEvent.initEvent('myEvent', true, true)
let btn = document.getElementsByTagName('button')[0]
btn.addEventListener('myEvent', function (e) {
console.log(e)
console.log(e.detail)
})
setTimeout(() => {
// 触发事件
btn.dispatchEvent(myEvent)
}, 2000)
# 内存泄露
内存泄漏 在使用一些内存之后,如果后面不再需要用到这些内存,但没有将它们及时释放掉,就称为内存泄漏。
# 常见的内存泄露
# 意外的全局变量
未定义的变量会在全局对象创建一个新变量。
function f() {
eat = "fish"; //未定义会创建在全局中
}
f();
解决方案: 在JavaScript头部加上use strict,使用严格模式避免意外的全局变量
# 被遗忘的定时器或回调函数
定义定时器(setTimeout / setInterval)后没有移除(clearTimeout / clearInterval)
# 闭包
# DOM元素的引用
闭包的关键是匿名函数可以访问父级作用域的变量,让变量不被回收。
# 垃圾回收机制
# 引用计数
跟踪每个值被引用的次数,声明一个变量后,这个变量每被其他变量引用一次,就加 1 ,如果变量引用释放了,就减 1,当引用次数为 0 的时候,对象就被清理。但这个有个循环引用的弊端,所以应用的比较少。
# 标记清除
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量以及被环境中的变量引用的标记,而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
# 垃圾收集性能优化
# 并发与并行
# 并发
宏观概念,指一段时间内通过任务切换完成两个任务,这种情况称为并发。
# 并行
微观概念,指同时执行多个任务的情况。
# 阻塞
# 同步阻塞
小明一直盯着下载进度条,到 100% 的时候就完成。
js就是属于这种类型的
# 同步非阻塞
小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。(轮询)
# 异步阻塞
小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音
# 异步非阻塞
仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。
# 模块化
# ES6模块
- 编译时加载。
Es 6模块不是对象,而是通过export命令显式指定输出的代码。在import时可以指定加载某个输出值,而不是加载整个模块。 - 模块输出的是值的引用。它是动态引用的,并且不会缓存值,模块里面的变量绑定其所在的模块。
# CommonJS
- 模块是运行时加载。
CommonJs模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法。 - 模块输出的是一个值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值。
# AMD
# 柯里化函数
# 柯里化
在一个函数中,首先会接受一些参数,然后再返回一个新的函数的技术,称为函数的柯里化。
作用:通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。
const add = function add(x) {
return function (y) {
return x + y
}
};
let add1 = add(2);
console.log(add1(3)); // 5
# AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
通常,在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中,具体的实现技术有很多,本节我们通过扩展 Function.prototype 来做到这一点。
# requestAnimationFrame
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
# 实现currying函数
# 异步编程
# 回调函数
优点: 简单、容易理解。
**缺点:**不利于代码阅读和维护,各部分之间高度耦合,而且每个任务只能指定一个回调函数。
# Promise
# 概念
Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。本质上Promise是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
- 三态:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected),状态的改变只能是单向的,且变化后不可在改变。
- 一个 Promise 必须提供一个 then 方法以访问其当前值、终值和据因,且返回 promise 对象。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done~');
});
}
timeout(100).then((value) => {
console.log(value);
});
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
# 解决回调地狱
由于它的then方法和catch、finally方法会返回一个新的Promise,所以可以允许我们链式调用,解决传统的回调地狱问题。
# 原理
Promise的原理是状态管理。Promise一共由三种状态: pending、fulfilled、rejected, 而状态是不可逆的,Promise正是通过修改状态的方式,在合适的时机触发相应状态的回调来达到处理异步的目的。通过then的链式调用也避免了回调地域的问题。
# finally
# all && race
# Promise.all()
Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
- 如果一组异步操作中有一个异常都不会进入.then()的第一个回调函数参数中。
# Promise.race()
Promise.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
# Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。Generator 函数是一个状态机,封装了多个内部状态。
function* helloWorldGenerator() {
yield 5+15;
yield 'world';
return 'ending';
}
let hw = helloWorldGenerator();
console.log(hw.next()); // {value: 20,done: false}
console.log(hw.next()); // {value: world,done: false}
console.log(hw.next()); // {value: ending,done: true}
# async/await
async是ES2017引进的函数,是Generator函数的语法糖。它将Generator函数的*号替换成async,将yield替换成await。
# async
async函数内部return语句返回的值,会成为then方法回调函数的参数。只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
async function f() {
return 'Fendy';
}
f().then(value => {
console.log(value); // Fendy
});
# await
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
//简易的sleep版本
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
// 用法
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
one2FiveInAsync();
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
- 任何一个
await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function common () {
await Promise.reject('error~')
return Promise.resolve('Fendy')
}
- 紧跟着await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。
# 错误处理
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个
await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {}
return 'hello world';
}
f().then(v => console.log(v))
// hello world
# Ajax
Ajax是可以实现在不必刷新整个页面的情况下实现局部更新,与服务器进行异步通讯的技术。
# XMLHTTPRequest
# 属性
# readyState
0:未初始化(Uninitialized)。尚未调用 open()方法。 1:已打开(Open)。已调用 open()方法,尚未调用 send()方法。 2:已发送(Sent)。已调用 send()方法,尚未收到响应。 3:接收中(Receiving)。已经收到部分响应。 4:完成(Complete)。已经收到所有响应,可以使用了。
# status
服务器返回的HTTP状态码
# API
# open()
创建http请求
- 第一个参数:定义请求的方式(get/post)
- 第二个参数:提交的地址
- 第三个参数:是否异步(默认为true)
# send()
# setRequestHeader()
# abort()
# getAllResponseHeaders()
# getResponseHeader()
# Ajax原理
// 1、创建XMLHttpRequest对象
let xhr = new XMLHttpRequest()
// 2、监听状态改变
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
alert(xhr.responseText)
} else {
alert('Request was unsuccessful: ' + xhr.status)
}
}
}
// 3、建立xhr請求
xhr.open('get', 'example.txt', true)
// 4、发送请求
xhr.send()
另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
// 出错了
// hello world
# 防抖节流
# 防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
eg:当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,即持续触发滚动事件的过程中,handle一直没有执行。
const debounce = (handleFn, time, args) => {
let timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
handleFn.apply(this, args)
}, time)
}
}
应用场景:
- input实时搜索
- 浏览器的resize
- scroll
# 节流
原理: 将即将被执行的函数用setTimeout 延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来调用该函数的请求。
const throttle = (handleFn, time, args) => {
let canRun = true
return function () {
if (!canRun) { return }
// 防止连续执行
canRun = false
setTimeout(() => {
handleFn.apply(this, args)
canRun = true
}, time)
}
}
应用场景:
- 表单重复提交
- 滚动加载
# 正则表达
# 匹配邮箱
const email = 'zf@5.cn'
const cmp=/[a-zA-Z0-9-_]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+/
console.log(cmp.test(email))
# 匹配手机号码
const regex= /^1[3-9]\d{9}$/
console.log(regex.test('15625701754'))
# 根据name获取cookie中的值
// (^| )以''或' '开头
// ([^;]*)匹配不包括;的所有内容
const getCookieByName = (name) => {
const cookie =cookies.match(new RegExp(`(^| )${name}=([^;]*)`))
if (cookie) { return decodeURIComponent(cookie[2]) }
}
# 获取Url参数
// [^?&]查找任何不在方括号之间的字符
const getUrlQuery = (url) => {
const search = url.split('?')[1]
const reg =/([^?&=]+)=([^&]+)/g
if (!search) {
return {}
}
const q = {}
url.replace(reg, (_, k, v) => {
q[k] = v
})
return q
}
# 手写题
# 浅拷贝&深拷贝
浅拷贝: 复制的是变量的地址,如果原地址指向的内容发生改变,那么浅拷贝出来的对象也会发生相应的改变。
深拷贝: 增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
# 浅拷贝(如下方法第一层是深拷贝)
# Object.assign
Object.assgin只会拷贝所有的属性值到新的对象中,但如果属性值是一个对象的话,拷贝的是地址,所以并不是深拷贝。
let obj = {
a: 1,
b: {
food: 'food',
bar: 'bar'
}
};
let copyObj = Object.assign({}, obj);
console.log(copyObj);
//{a:1,b:{food:'food',bar:'bar'}}
obj.a = 4;
copyObj.b.food = 'fish';
console.log(copyObj.a);
// 1 -> 拷贝的是值,不会随着obj修改而修改。
console.log(obj.b.food);
// fish -> 拷贝的是地址,obj和copyObj的地址指向同一个值,所以只要其中一个修改了,另外的也会跟着修改
。
# 展开运算符
展开运算符(...)与Object.assign一样:拷贝的属性值为对象时,只拷贝地址。
let obj = {
a: 1,
b: {
food: 'food',
bar: 'bar'
}
};
let copyObj = {...obj};
console.log(copyObj); //{a:1,b:{food:'food',bar:'bar'}}
obj.a = 4;
copyObj.b.food = 'fish';
console.log(copyObj.a); //1
console.log(obj.b.food); //fish
# slice
const arr=[1,[2,3,4],5]
const newArr=arr.slice()
newArr[0]=0
newArr[1][1]=6
console.log(arr); // [ 1, [ 2, 6, 4 ], 5 ]
console.log(newArr);// [ 0, [ 2, 6, 4 ], 5 ]
# concat
同上
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);//[ 1, 2, 3 ]
# push+apply
let arr=[1,2,3],newArr=[];
[].push.apply(newArr,arr);
# 手动实现
const shallowClone = (target) => {
if (target !== null && typeof target === 'object') {
const cloneTarget = Array.isArray(target) ? [] : {}
for (const targetKey in target) {
cloneTarget[targetKey] = target[targetKey]
}
return cloneTarget
} else {
return target
}
}
const Fendy = {
name: 'Fendy',
age: 18,
likes: [
{
name: 'food',
age: 16
},
{
name: 'water',
age: 18
}
]
}
const nowFendy = shallowClone(Fendy)
nowFendy.likes[0].age = 21
nowFendy.name = 'nowFendy'
console.log(Fendy)
/**
{
name: 'Fendy',
age: 18,
likes: [ { name: 'food', age: 21 }, { name: 'water', age: 18 } ]
}
**/
console.log(nowFendy)
/**
{
name: 'nowFendy',
age: 18,
likes: [ { name: 'food', age: 21 }, { name: 'water', age: 18 } ]
}
**/
# 深拷贝
# JSON.stringify
let person = {
age: 18,
lover:{
name: 'sn'
}
};
let deepCopy = JSON.parse(JSON.stringify(person));
console.log(deepCopy); // age: 18,lover:{ name: 'sn' }
deepCopy.lover.name = 'ky';
console.log(deepCopy.lover.name); // ky
console.log(person.lover.name); // sn
# 递归
- 判断参数是否为对象
- 创建深拷贝副本
- 循环参数的key,并且判断value是否为Object
- 返回深拷贝副本
const deepCopy = (obj) => {
if (obj !== null && typeof obj === 'object') {
const deepObj = {};
for (const objKey in obj) {
if (typeof obj[objKey] === 'object') {
deepObj[objKey] = deepCopy(obj[objKey])
} else {
deepObj[objKey] = obj[objKey]
}
}
return deepObj
}else {
return obj
}
}
# Object.create
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__(会返回一个新对象,带着指定的原型对象和属性)
- 创建新函数
- 将新函数的原型绑定指定的值
- 返回新函数的实例
// 手写 Object.create()
function _create(obj) {
if (obj === null && typeof obj != 'object') {
throw new Error(`${obj}不是有效的对象`);
} else {
function Temp() {}
Temp.prototype = obj;
return new Temp;
}
}
# instanceof
- 判断参数是否为对象
- 获取参数的原型对象
- 通过循环不断更新原型对象,同时判断原型对象是否等于第二个参数的原型
function myInstanceof(left,right) {
// 如果是基本数据类型,直接返回false
if (left==null || typeof left!=='object') return false;
//getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto===null) return false;
if (proto===right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
# new
- 创建新对象
- 将新对象的原型设置构造函数的prototype对象
- 将构造函数的作用域赋给新的对象,并执行构造函数
- 返回新对象
function _new(Fn, ...args) {
const obj = {};//1、创建新的对象
obj.__proto__ = Fn.prototype;//2、将新对象的原型关联到构造函数的原型对象
const res = Fn.call(obj, ...args);//3、让构造函数的this指向新的对象,并执行构造函数
return typeof res === 'object' ? res : obj;//4、如果构造函数没有返回值,就返回创建的对象。
}
function Person(age) {
this.age = age;
}
Person.prototype.say = function (age) {
console.log("Your age is " + this.age)
}
let person = _new(Person,13)
person.say()
# apply、call、bind
# apply
Function.prototype._apply = function (context, args) {
if (typeof this !== 'function') {
// 调用的若不是函数则报错
throw new TypeError('Error');
}
let result = null;
// context为null时,context设置为window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
if(arguments[1]){
result = context.fn(...arguments[1]);
}else {
result = context.fn();
}
// 删除该方法
delete context.fn;
return result;
};
# call
Function.prototype._call = function (context) {
if (typeof this !== 'function') {
return new Error('Error')
}
let res = null, args = [...arguments].slice(1)
context = context || window
context.fn = this
res = context.fn(...args)
delete context.fn
return res
}
# Ajax
// 1、创建XMLHttpRequest对象
let xhr = new XMLHttpRequest()
// 2、监听状态改变
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
alert(xhr.responseText)
} else {
alert('Request was unsuccessful: ' + xhr.status)
}
}
}
// 3、建立xhr請求
xhr.open('get', 'example.txt', true)
// 4、发送请求
xhr.send()
# 各种区别
# null & undefined
语义不一样。null表示定义了,但是值为空。undefined表示未定义。
转换成数字结果不一样。Number(null)为0,Number(undefined)为NaN。
# Object.is()和===
Object在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是+0和-0,NaN和NaN。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
# class和function
- **语义上:**class 也是一个语法糖,本质还是基于原型链,class 语义化和编码上更加符合面向对象的思维。
- **改变执行上下文:**function可使用call、apply、bind来改变执行上下文,但是class不可以。因为class在转换成ES5的时候做了一层代理,来禁止这种行为。
# isNaN和Number.isNaN
- isNaN:除了判断NaN为true,还会把不能转成数字的判断为true,例如'xxx'
- Number.isNaN:只有判断NaN时为true,其余情况都为false
# Object.assign和扩展运算符
相同点:两者都是浅拷贝
不同点:
- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了目标对象,因此会触发 ES6 setter。
- 扩展运算符返回参数的浅拷贝对象,不会改变参数本身。不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。
# encodeURI和encodeURIComponent
encodeURI对整个URL进行编码,而URL的特定标识符不会被转码。
encodeURIComponent,对URL中的参数进行编码,因为参数URL也可以是一个参数,所以会对URL的特定标识符进行转码。
const url = 'https://blog.fendy5.cn/login?redirect=https://blog.fendy5.cn/s/算法' console.log(encodeURI(url)) // https://blog.fendy5.cn/login?redirect=https://blog.fendy5.cn/s/%E7%AE%97%E6%B3%95 console.log(encodeURIComponent(url)) // https%3A%2F%2Fblog.fendy5.cn%2Flogin%3Fredirect%3Dhttps%3A%2F%2Fblog.fendy5.cn%2Fs%2F%E7%AE%97%E6%B3%95
# typeof和instanceof
# 一些bug
# 0.1+0.2!=0.3
原因:
由于计算机是通过二进制来存储数据,很多十进制小数用二进制表示都会是无限循环的。因为JS采用浮点数标准,导致会裁剪掉我们的数字。裁剪后会出现精度消失的问题,所以0.1就不再是0.1了。
解决:
# [] == ![] 为何为true
由于 ! 的优先级高于 == ,![] 首先会被转换为false,然后根据Boolean转换原则,false将会转换为Number类型,即转换为 0,然后左侧的 [] 转换为原始类型,也为 0 ,所以最终结果为 true
# [] ===[] 为何为false
运算符对于对象(数组也是对象)只看双方地址,地址一样则返回true。