目录
ES6新增
数据类型
基本数据类型
引用数据类型
Null,NaN,Undefined
toString,valueOf
==,===,Object.is()
判断数据类型:typeof运算符,instance of运算符,isPrototypeOf() 方法,constructor,Object prototype
instance of (⭐手写)
new (⭐手写)
类型转换
转换为布尔值
type of null
事件
DOM事件流(event flow )
事件委托/代理(⭐手写)
执行上下文/作用域和作用链
this
JS预解析(变量提升)
var,let 和 const关键字
原型链
Iterator,for in,for of,forEach,map循环遍历
Iterator
for of
for in
forEach
map
匿名函数、箭头函数、构造函数
匿名函数
箭头函数
构造函数
this
call、apply、bind改变this
call (⭐手写)
apply(⭐手写)
bind(⭐手写)
闭包(closure)
Map
Set
set判断值相等的机制
数组去重 (⭐手写)
Array
Array.filter(⭐手写)
Array.map(⭐手写)
Array.reduce(⭐手写)
String
高阶函数和函数的珂里化Currying
Arguments对象
深浅拷贝
深拷贝(⭐手写)
严格模式
防抖 (⭐手写)
节流(⭐手写)
防抖、节流应用
垃圾回收(GC)
内存分配
回收策略
内存泄漏
宏任务、微任务、Event-Loop
setImmediate与setTimeout的区别
JS延迟加载的方式
ES6新增
- 数据类型:基本数据类型Symbol,引用数据类型Set ,Map
- 运算符:变量的解构赋值,对象和数组新增了扩展运算符
- 字符串方法:${ },需要配合单反引号好完成字符串拼接的功能,eg:`http://localhost:3000/search/users?q=${keyWord}`
- 块级作用域:let,const
- 原生提供 Proxy 构造函数,用来生成 Proxy 实例
- 定义类的语法糖(class)
- 模块化import/export
- 生成器(Generator)和遍历器(Iterator)
数据类型
基本数据类型
ES5:Null,Undefined,Number,String,Boolean
ES6新增:Symbol(仅有目的:作为对象属性的标识符,表示唯一的值)
var obj = {};
obj[Symbol("a")] = "a";
//会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,
//如果找到了,则返回它,
//否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
Symbol.for(key);
Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol
ES10新增:BigInt(表示任意的大整数)
let bnum=1684424684321231561n //方式1:数组后加n
bunm=BigInt("1684424684321231561")//方式2:调用BigInt
存储在栈(大小固定,先进后出)
引用数据类型
Object,function,Array,Date,RegExp,ES6新增:Set,MAP
地址存储在栈,内容存储在堆(树形结构,队列,先进先出)
Null,NaN,Undefined
- null:空对象,一般作为对象的初值
- Nan:浮点数中表示未定义或不可表示的值,例如0/0、∞/∞、∞/−∞、−∞/∞、−∞/−∞
- undefined:未定义,声明但未定义,例如,形参未传参,获取return的函数返回,对象属性名不存在
toString,valueOf
javascript中所有数据类型都拥有valueOf和toString这两个方法,null和undefined除外
- valueOf偏向于运算,toString偏向于显示
对象字面量表达式是加 ():({}).toString()
- valueOf:除了Date其他的都是返回数据本身
==,===,Object.is()
- ==:自动数据类型转换
强制转换规则
- string和number,string->number,
- 其他类型和boolean,bool->number
- 对象和非对象,对象先调用 ToPrimitive 抽象操作(调用
valueOf()或
toString()
) - null==undefined值转为Boolean值false
- NaN!=NaN
- ===:严格模式,不进行自动数据类型转换,比较的是栈中值(即基本数据类型的值,或者引用数据类型的地址)
- Object.is():在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
判断数据类型:typeof运算符,instance of运算符,isPrototypeOf()
方法,constructor,Object prototype
- typeof:判断 基本数据类型
- instance of:判断 引用数据类型,在其原型链中能否找到该类型的原型
isPrototypeOf()
:在表达式 "object instanceof AFunction
"中,object
的原型链是针对AFunction.prototype
进行检查的,而不是针对AFunction
本身。
Foo.prototype.isPrototypeOf(baz)
- constructor:判断 所有数据类型(不包含继承引用数据类型的自定义类型)
(数据).constructor === 数据类型
- Object.prototype.toString.call():Object 对象的原型方法 toString 来判断数据类型:
instance of (⭐手写)
第一个实例参数是否在第二个函数参数的原型链上
- 获取首个对象参数的原型对象
- 获取Fn函数的原型对象
- 进入死循环,当两个参数的原型对象相等时返回true
- 当两个参数的原型对象不相等时获取首个对象参数原型的原型并且循环该步骤直到null时返回false
const _instanceof = (target, Fn) => {
let proto = target.__proto__
let prototype = Fn.prototype
while(true) {
if(proto === Fn.prototype) return true
if(proto === null) return false
proto = proto.__proto__
}
}
const _instanceof = (target, Fn) => {
return Fn.prototype.isPrototypeOf(target);
}
new (⭐手写)
"_new"函数,该函数会返回一个对象,
该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:
- 创建一个新对象
- 获取函数参数
- 将新对象的原型对象和函数参数的原型连接起来
- 将新对象和参数传给构造器执行
- 如果构造器返回的不是对象,那么就返回第一个新对象
const _new = function() {
const object1 = {}
const Fn = [...arguments].shift()
object1.__proto__ = Fn.prototype
const object2 = Fn.apply(object1, arguments)
return object2 instanceof Object ? object2 : object1
}
类型转换
- 转换为数字:
Number():可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回NaN
parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix是2-36之间的整数,表示被解析字符串的基数。
parseFloat(string):解析一个参数并返回一个浮点数
隐式转换:
-
let str = '123'
-
let res = str - 1 //122
-
str+1 // '1231'
-
+str+1 // 124
- 转换为字符串
.toString() ⚠️注意:null,undefined不能调用
String() 都能转
隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串
转换为布尔值
Boolean():0, ''(空字符串), null, undefined, NaN会转成false,其它都是true
隐式转换 !!
type of null
typeof null 的结果是Object。
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits)
000: object - 当前存储的数据指向一个对象。
null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
事件
文档和浏览器窗口中发生的特定交互
DOM事件流(event flow )
先捕获再冒泡。存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
- 事件捕获:由外往内,从事件发生的根节点开始,逐级往下查找,一直到目标元素。
- 事件冒泡:由内往外,从具体的目标元素触发,逐级向上传递,直到根节点。
element.addEventListener(event, function[, useCapture]);
//useCapture 默认为false,即冒泡阶段调用事件处理函数,
//为ture时,在事件捕获阶段调用处理函数
事件委托/代理(⭐手写)
事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。
应用:
1000
个button
注册点击事件。如果循环给每个按钮添加点击事件,那么会增加内存损耗,影响性能
好处:
-
替代循环绑定事件的操作,减少内存消耗,提高性能。比如:
ul
上代理所有li
的click
事件。 -
简化了
dom
节点更新时,相应事件的更新。比如:- 不用在新添加的
li
上绑定click
事件。 - 当删除某个
li
时,不用解绑上面的click
事件。
- 不用在新添加的
缺点:
- 事件委托基于冒泡,对于不冒泡的事件不支持。
- 层级过多,冒泡过程中,可能会被某层阻止掉。
- 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在
table
上代理td
,而不是在document
上代理td
。
阻止事件冒泡:event.stopPropagation() .stop修饰符
1. 给"ul"标签添加点击事件
2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
注意:
1. 必须使用DOM0级标准事件(onclick)
target表示当前触发事件的元素
currentTarget是绑定处理函数的元素
只有当事件处理函数绑定在自身的时候,target才会和currentTarget一样
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<script type="text/javascript">
document.querySelector('ul').onclick=event=>{
event.target.innerText+='.'
}
</script>
执行上下文/作用域和作用链
作用域就是一个变量可以使用的范围
C/C++中有块级作用域,变量在声明它们的代码段之外是不可见的
javascript的作用域是相对函数而言的,可以称为函数作用域
全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
js为每一个执行环境关联了一个变量对象。环境中 定义的 所有变量和函数 都保存在这个对象中。
全局执行环境被认为是window对象
这样由多个执行上下文的变量对象构成的链表就叫做作用域链,从某种意义上很类似原型和原型链。
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域
作用域链和原型继承查找时的区别:
查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回undefined
查找的属性在作用域链中不存在的话就会抛出ReferenceError。
this
若是在全局环境(例如,普通函数,匿名函数)中,则this指向window;( 严格模式下this会指向 undefined)
在对象里调用的this,指向调用函数的那个对象,
JS预解析(变量提升)
预编译/解析:JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行定义,创建执行上下文,初始化一些代码执行时需要用到的对象。
var,let 和 const关键字
在 ES6 之前,JavaScript 只有两种作用域: 全局变量 与 函数内的局部变量。
ES6新增,块级作用域(由大括号包裹,比如:if(){},for(){}等)
- var:可以跨块访问, 不能跨函数访问,允许重复声明,变量提升
- let、const:只能在块作用域里访问,不允许在相同作用域中重复声明,不存在变量提升
- const :声明一个只读的常量,使用时必须初始化(即必须赋值),一旦声明,常量的值就不能改变,(即,栈中的值不能变,引用类型,内存地址不能修改,可以修改里面的值。)。
原型链
console.log(Person.prototype);
// {constructor: ƒ}
// constructor: ƒ Person()
// arguments: null
// caller: null
// length: 0
// name: "Person"
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: 09-原型对象.html:8
// [[Scopes]]: Scopes[1]
// __proto__: Object
(引用类型统称为object类型)
所有引用类型
都有一个__proto__(隐式原型)
属性,属性值是一个普通的对象
所有函数
都有一个prototype(原型)
属性,属性值是一个普通的对象
构造函数和类同名
_ _ proto _ _
person.prototype.isPrototypeOf(stu)
只要调用对象在传入对象的原型链上都会返回true
首先,fn的构造函数是Foo()。所以:
fn._ _ proto _ _=== Foo.prototype
又因为Foo.prototype是一个普通的对象,它的构造函数是Object,
Foo.prototype=object
所以:
Foo.prototype._ _ proto _ _=== Object.prototype
【原型和原型链】什么是原型和原型链_TowYingWang的博客-CSDN博客_原型和原型链
Iterator,for in,for of,forEach,map循环遍历
Iterator
一种接口,为各种不同的数据结构提供统一的访问机制
例如Array.prototype[@@iterator]()
Array
对象的 @@iterator
方法实现了迭代协议,并允许数组被大多数期望可迭代的语法所使用,例如展开语法和 for...of 循环。它返回一个迭代器,生成数组中每个索引的值。
for of
["a", "b", "c", "d"];for…of 循环读取键值// a b c d
支持迭代协议的数据结构(数组、字符串、Set、Map 等),不包括对象。
对于字符串,类数组,类型数组的迭代,循环内部调用的是数据结构的Symbol.iterator
方法。
for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]
for in
["a", "b", "c", "d"];for…in 循环读取键名 // 0 1 2 3
适用于遍历对象的 公有 可枚举属性,无法遍历 symbol 属性
hasOwnProperty() 方法来判断属性是否来自对象本身,并避免遍历原型链上的属性。
var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = 'red';
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
// Output:
// "obj.color = red"
forEach
arr.forEach(value[,index,默认隐藏参数arr])
适用于需要知道索引值的数组遍历,但是不能中断( break 和 return )
如果需要跳出循环可以使用 some() 或 every() 方法
const isBelowThreshold = (currentValue) => currentValue < 30;
const array1 = [1, 30, 39, 29, 10, 13];
array1.forEach(element => console.log(element));
console.log(array1.every(isBelowThreshold));
// Expected output: false
//是不是至少有 1 个元素
console.log(array1.some(isBelowThreshold));//空数组,则返回false。
// Expected output: true
map
map 方法,基本用法与 forEach 一致
- forEach()方法不会返回执行结果,而是undefined
- map()方法会得到一个新的数组并返回
- 同样的一组数组,map()的执行速度优于 forEach()(map() 底层做了深度优化)
匿名函数、箭头函数、构造函数
匿名函数
有关键词 function,没有函数名。
//声明匿名函数
let myFun = function( a,b ){
console.info( a+b);
};
//执行
myFun( 10,30 );
//等同于 立即执行匿名函数
(function(a,b){
console.info( a+b );
})(10,30);
箭头函数
连function都没有的匿名函数,箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
箭头函数不绑定arguments,取而代之用rest参数解决
不可以使用yield,因此箭头函数不能用作Generator函数。
没有原型prototype,没有super用于访问原型属性。
//传递给getVal函数内的this并不是调用者自身,而是外部的this,即window
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}
obj.getVal(); // 2
构造函数
function Person(name,id)
{
this.name=name;
this.id=id;
this.sayHi=function() {
alert("Hi")
}
}
var p= new Person('参宿','7');
- 习惯上首字母大写
- 使用new关键字进行调用
this
普通函数的this在运行时创建,匿名函数this指向window,箭头函数的this是定义时确定。
箭头函数不能用作构造函数:
因为构造函数的this指向实例对象,但是箭头函数无法对创建出来的实例进行this绑定
也不能使用call()、apply()、bind() 去改变this的指向。
call、apply、bind改变this
call()和apply()唯一区别:
call()
接受的是一个参数列表
apply()
方法接受的是一个包含多个参数的数组。
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
bind:语法和call一样,区别在于call立即执行,bind等待执行,bind不兼容IE6~8
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
call (⭐手写)
// 给function的原型上面添加一个 _call 方法
Function.prototype._call = function (context) {
// 判断调用者是否是一个函数 this 就是调用者
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
// 如果有 context 传参就是传参者 没有就是window
context = context || window
// 保存当前调用的函数
context._this = this
// 截取传过来的参数
/*
arguments
a: 1
fn: ƒ fns()
*/
// 通过 slice 来截取传过来的参数
const local = [...arguments].slice(1)
// 传入参数调用函数
let result = context._this(...local)
// 删属性
delete context._this
return result
}
let obj = { a: 1 }
function fns(a, b) {
console.log(a, b);
console.log(this)
}
fns._call(obj, 23, 555)
apply(⭐手写)
// 给function的原型上面添加一个 _apply 方法
Function.prototype._apply= function (context) {
// 判断调用者是否是一个函数 this 就是调用者
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
// 如果有 context 传参就是传参者 没有就是window
context = context || window
// 保存当前调用的函数
context._this = this
// 截取传过来的参数
/*
arguments
a: 1
fn: ƒ fns()
*/
//!!!!!!!!!!!!!!与call的唯一区别!!!!!!!!!!!
// 这里开始判断传入的参数是否存在,此时参数是一个数组形式[thisArg,[传参]]
// 那么如果arguments[1]即传参存在的时候,就是需要传参调用保存的函数
// 如果不存在就直接调用函数
if (arguments[1]) {
result = context._this(...arguments[1])//!!!!将数组展开!!!!
} else {
result = context._this()
}
//!!!!!!!!!!!!!!与call的唯一区别!!!!!!!!!!!
// 删属性
delete context._this
return result
}
let obj = { a: 1 }
function fns(a, b) {
console.log(a, b);
console.log(this)
}
fns._call(obj, 23, 555)
bind(⭐手写)
Function.prototype._bind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('what is to be a function')
}
var _this = this; // 保存调用bind的函数
var context = context || window; // 确定被指向的this,如果context为空,执行作用域的this就需要顶上喽
return function(){
return _this.apply(context, [...arguments].slice(1)); // 如果只传context,则[...arguments].slice(1)为空数组
}
};
var obj = {
name: 1,
getName: function(){
console.log(this.name)
}
};
var func = function(){
console.log(this.name);
}._bind(obj);
func(); // 1
闭包(closure)
类比背包,当一个函数被创建并传递或从另一个函数返回时,它会携带一个背包。背包中是函数声明时作用域内的所有变量。
var name = '余光';
function foo() {
console.log(name); // 余光
}
(function (func) {
var name = '老王';
func()
})(foo); // 余光
因为js作用域生命周期在于内部脚本是否全部执行完毕才会销毁,并且不会带到父级作用域;
当函数内部返回一个函数,子函数没在父级作用域内完成整个生命周期的话,父级函数是没办法完成一整个生命周期的,闭包正是利用这一点卡住了父级函数的作用域。
因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
面试的时候,直接回答函数嵌套函数,且内部函数调用父级作用域的变量就可以称之为闭包了。
1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
- 闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
- 滥用闭包容易内存泄漏。
- 使用场景 : 防抖、节流、函数套函数避免全局污染
Map
保存键值对,任何值(对象或者基本类型)都可以作为一个键或一个值。
Map的键可以是任意值,包括函数、对象或任意基本类型。
object的键必须是一个String或是Symbol 。
const contacts = new Map()
contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
contacts.has('Jessie') // true
contacts.get('Hilary') // undefined
contacts.delete('Jessie') // true
console.log(contacts.size) // 1
function logMapElements(value, key, map) {
console.log(`m[${key}] = ${value}`);
}
new Map([['foo', 3], ['bar', {}], ['baz', undefined]])
.forEach(logMapElements);
// Expected output: "m[foo] = 3"
// Expected output: "m[bar] = [object Object]"
// Expected output: "m[baz] = undefined"
Set
值的集合,且值唯一
虽然NaN !== NaN,但set中NaN 被认为是相同的
let setPos = new Set();
setPos.add(value);//Boolean
setPos.has(value);
setPos.delete(value);
function logSetElements(value1, value2, set) {
console.log(`s[${value1}] = ${value2}`);
}
new Set(['foo', 'bar', undefined]).forEach(logSetElements);
// Expected output: "s[foo] = foo"
// Expected output: "s[bar] = bar"
// Expected output: "s[undefined] = undefined"
set判断值相等的机制
//Set用===判断是否相等
const set= new Set();
const obj1={ x: 10, y: 20 },obj2={ x: 10, y: 20 }
set.add(obj1).add(obj2);
console.log(obj1===obj2);//false
console.log(set.size);// 2
set.add(obj1);
console.log(obj1===obj1);//true
console.log(set.size);//2
数组去重 (⭐手写)
// Use to remove duplicate elements from the array
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]
Array
//创建字符串
//join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串
//如果数组只有一个元素,那么将返回该元素而不使用分隔符。
Array.join()
Array.join(separator)
//################创建数组:
//伪数组转成数组
Array.from(arrayLike, mapFn)
console.log(Array.from('foo'));
// Expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// Expected output: Array [2, 4, 6]
console.log( Array.from({length:3},(item, index)=> index) );// 列的位置
// Expected output:Array [0, 1, 2]
//################原数组会改变:
arr.reverse()//返回翻转后的数组
// 无函数
arr.sort()//默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16
// 比较函数
arr.sort(compareFn)
function compareFn(a, b) {
if (在某些排序规则中,a 小于 b) {
return -1;
}
if (在这一排序规则下,a 大于 b) {
return 1;
}
// a 一定等于 b
return 0;
}
//升序
function compareNumbers(a, b) {
return a - b;
}
//固定值填充
arr.fill(value)
arr.fill(value, start)
arr.fill(value, start, end)
//去除
array.shift() //从数组中删除第一个元素,并返回该元素的值。
array.pop() //从数组中删除最后一个元素,并返回该元素的值。此方法会更改数组的长度。
array.push() //将一个或多个元素添加到数组的末尾,并返回该数组的新长度
//unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度
array.unshift(element0, element1, /* … ,*/ elementN)
//粘接,通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1)
array.splice(start, deleteCount, item1, item2...itemN)
//################原数组不会改变:
//切片,浅拷贝(包括 begin,不包括end)。
array.slice()
array.slice(start)
array.slice(start, end)
//展平,按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
array.flat()//不写参数默认一维
array.flat(depth)
//过滤器,函数体 为 条件语句
// 箭头函数
filter((element) => { /* … */ } )
filter((element, index) => { /* … */ } )
filter((element, index, array) => { /* … */ } )
array.filter(str => str .length > 6)
//遍历数组处理
// 箭头函数
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })
array.map(el => Math.pow(el,2))
//map和filter同参
//接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
// 箭头函数
reduce((previousValue, currentValue) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex) => { /* … */ } )
reduce((previousValue, currentValue, currentIndex, array) => { /* … */ } )
reduce((previousValue, currentValue) => { /* … */ } , initialValue)
reduce((previousValue, currentValue, currentIndex) => { /* … */ } , initialValue)
array.reduce((previousValue, currentValue, currentIndex, array) => { /* … */ }, initialValue)
//一个“reducer”函数,包含四个参数:
//previousValue:上一次调用 callbackFn 时的返回值。
//在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,
//否则为数组索引为 0 的元素 array[0]。
//currentValue:数组中正在处理的元素。
//在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],
//否则为 array[1]。
//currentIndex:数组中正在处理的元素的索引。
//若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。
//array:用于遍历的数组。
//initialValue 可选
//作为第一次调用 callback 函数时参数 previousValue 的值。
//若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;
//否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
// Expected output: 10
Array.filter(⭐手写)
Array.prototype._filter = function(Fn) {
if (typeof Fn !== 'function') return
const array = this
const newArray = []
for (let i=0; i<array.length; i++) {
const result = Fn.call(null, array[i], i, array)
result && newArray.push(array[i])
}
return newArray
}
Array.map(⭐手写)
Array.prototype._map = function(Fn) {
if (typeof Fn !== 'function') return
const array = this
const newArray = []
for (let i=0; i<array.length; i++) {
const result = Fn.call(null, array[i], i, array)
//##########与filter的唯一不同
newArray.push(result)
}
return newArray
}
Array.reduce(⭐手写)
Array.prototype._reduce = function(fn,initialValue = 0){
if(typeof fn !== 'function') return;
let res = initialValue
this.forEach((value,index,arr)=>{
res = fn(res,value,index,arr)
})
return res
}
String
str.charAt(index)//获取第n位字符
str.charCodeAt(n)//获取第n位UTF-16字符编码 (Unicode)A是65,a是97
String.fromCharCode(num1[, ...[, numN]])//根据UTF编码创建字符串
String.fromCharCode('a'.charCodeAt(0))='a'
str.trim()//返回去掉首尾的空白字符后的新字符串
str.split(separator)//返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
const str = 'The quick brown fox jumps over the lazy dog.';
const words = str.split(' ');
console.log(words[3]);
// Expected output: "fox"
str.toLowerCase( )//字符串转小写;
str.toUpperCase( )//字符串转大写;
str.concat(str2, [, ...strN])
str.substring(indexStart[, indexEnd]) //提取从 indexStart 到 indexEnd(不包括)之间的字符。
str.substr(start[, length]) //没有严格被废弃 (as in "removed from the Web standards"), 但它被认作是遗留的函数并且可以的话应该避免使用。它并非 JavaScript 核心语言的一部分,未来将可能会被移除掉。
str.indexOf(searchString[, position]) //在大于或等于position索引处的第一次出现。
str.match(regexp)//找到一个或多个正则表达式的匹配。
const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.';
let regex = /[A-Z]/g;
let found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T", "I"]
regex = /[A-Z]/;
found = paragraph.match(regex);
console.log(found);
// Expected output: Array ["T"]
//match类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
var str = '123123000'
str.match(/\w{3}/g).join(',') // 123,123,000
str.search(regexp)//如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1
const paragraph = '? The quick';
// Any character that is not a word character or whitespace
const regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// Expected output: 0
str.repeat(count)//返回副本
str.replace(regexp|substr, newSubStr|function)//返回一个由替换值(replacement)替换部分或所有的模式(pattern)匹配项后的新字符串。
const p = 'lazy dog.Dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replace('dog', 'monkey'));
// "lazy monkey.Dog lazy"
let regex = /dog/i;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replace(regex, 'ferret'));
//"lazy ferret.Dog lazy"
regex = /d|Dog/g;
console.log(p.replace(regex, 'ferret'));
//"lazy ferretog.ferret lazy"
//当使用一个 regex 时,您必须设置全局(“g”)标志, 否则,它将引发 TypeError:“必须使用全局 RegExp 调用 replaceAll”。
const p = 'lazy dog.dog lazy';//如果pattern是字符串,则仅替换第一个匹配项。
console.log(p.replaceAll('dog', 'monkey'));
// "lazy monkey.monkey lazy"
let regex = /dog/g;//如果非全局匹配,则仅替换第一个匹配项
console.log(p.replaceAll(regex, 'ferret'));
//"lazy ferret.ferret lazy"
高阶函数和函数的珂里化Currying
高阶函数:参数 或者 返回值为函数
函数柯里化:返回值为函数,实现多次接收参数最后统一处理的函数编码
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参。
用途:延迟计算、参数复用、动态生成函数(都是闭包的用途)。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
Arguments
对象
是所有(非箭头)函数中都可用的局部变量。类似于Array
,但除了 length 属性和索引元素之外没有任何Array
属性。
function add() {
var sum =0,
len = arguments.length;
for(var i=0; i<len; i++){
sum += arguments[i];
}
return sum;
}
add() // 0
add(1) // 1
add(1,2,3,4); // 10
深浅拷贝
基本类型:内存区域存储的是值,不存在深拷贝和浅拷贝
引用类型:内存区域存储的是地址,浅拷贝只拷贝一层(内存地址),而深拷贝是层层拷贝(拷贝内容,新开辟内存)。
深拷贝(⭐手写)
function cloneDeep(arr = {}) {
// 终止递归 判断如果传进来的数据不是 object 或者 传进来的是一个 null 直接返回
if (!arr || typeof arr != 'object' || arr == null) return arr
// 用 instanceof 判断原型链上是否有该类型的原型 是 Array => [] ! Arrays =>{}
let result=arr instanceof Array ? [] : {}
// forin 循环对象的key值
for (const key in arr) {
// 对象 key 赋值 result
result[key] = cloneDeep(arr[key])
}
return result
}
严格模式
严格模式通过抛出错误来消除了一些原有静默错误。
- 严格模式下,不允许给未声明的变量赋值
严格模式修复了一些导致 JavaScript 引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
严格模式禁用了在 ECMAScript 的未来版本中可能会定义的一些语法。
防抖 (⭐手写)
触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,会重计算函数执行时间。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
防抖: <input id="input" type="text">
</body>
<script>
// 防抖的核心代码
function debounce(fun,time) {
let flag // 定义状态
return function () {
clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
flag = setTimeout(() => {
fun.call(this,arguments)//拿到正确的this对象,即事件发生的dom
}, time)
}
}
let val = debounce(function (val) {
console.log(val)
},1000)
// 监听拿到input输入的值
input.addEventListener('input', function (e) {
val(e.target.value)
})
</script>
</html>
节流(⭐手写)
连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
<body>
<button id="button">1秒执行一次</button>
</body>
<script>
/*
定时器版本的
fns 回调函数
time 间隔时间
function throttle(fun, time) {
let flag // 定义一个空状态
return function () { // 内部函数访问外部函数形成闭包
if (!flag) { // 状态为空执行
flag = setTimeout(() => {
fns.apply(this, arguments) // 改变this指向 把 event 事件对象传出去
flag = null
}, time)
}
}
}
*/
function throttle(fun, time) {
let last = 0
return function () {
let now = Date.now()
// 当前的值 减去上一次的值 >= 传过来的事件 执行
if (now - last >= time) {
fun.apply(this, arguments)
last = now
}
}
}
button.onclick = throttle((e) => {
console.log(e)
}, 1000)
</script>
防抖、节流应用
防止某一时间频繁触发
- 防抖debounce:time内只执行一次
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 节流throttle: 间隔time执行
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
垃圾回收(GC)
GC
即 Garbage Collection
浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出不可访问的值,然后释放内存,所以将不需要的对象设为null即可。
内存分配
-
First-fit
,找到第一个的大于等于size
的块立即返回 -
Best-fit
,遍历整个空闲列表,返回大于等于size
的最小分块 -
Worst-fit
,遍历整个空闲列表,找到最大的分块,然后切成两部分,一部分size
大小,并将该部分返回
Worst-fit
的空间利用率看起来是最合理,但实际上切分之后会造成更多的小块,形成内存碎片,所以不推荐使用,
First-fit
和 Best-fit
来说,考虑到分配的速度和效率 First-fit
是更为明智的选择
回收策略
标记清除(Mark-Sweep):最常用
(根对象,在浏览器环境中包括
全局Window对象
、文档DOM树
等)
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成1
- 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
优点
简单
缺点
- 内存碎片化,清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了
内存碎片
,存在内存分配的问题- 分配速度慢,因为即便是使用
First-fit
策略,其操作仍是一个O(n)
的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢
标记整理(Mark-Compact)
改善标记清除清除之后剩余的对象位置不变而导致的空闲内存不连续
标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存
引用计数(Reference Counting),早的一种垃圾回收算法
它把
对象是否不再需要
简化定义为 没有引用指向该对象(零引用),对象将被垃圾回收机制回收,目前很少使用这种算法了,因为它的问题很多跟踪记录每个变量值被使用的次数
当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
如果同一个值又被赋给另一个变量,那么引用数加 1
如果该变量的值被其他的值覆盖了,则引用次数减 1
当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
优点
引用值为 0 时,可以立即回收垃圾
缺点
计数器需要占内存
不知道被引用数量的上限
无法解决循环引用无法回收的问题,这也是最严重的
内存泄漏
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收
JS中拥有自动的垃圾回收机制,
宏任务、微任务、Event-Loop
js引擎会优先执行微任务,例如:网页加载完毕,但是图片没加载出来
- 微任务microtask(异步):可以理解为task执行完后立刻执行,Promise async/await。
- 宏任务macrotask: setTimeout,setInterval一类的定时事件,Ajax,DOM事件,script 脚本的执行、 I/O 操作、UI 渲染等。
例如:new Promise
实例化是同步,而then
中注册的回调才是异步执行的。
例如:等待的客户为宏任务,他的每个业务为微任务
每办理完一个业务,柜员就会问当前的客户,是否还有其他需要办理的业务。(检查还有没有微任务需要处理)
而客户明确告知说没有事情以后,柜员就去查看后边还有没有等着办理业务的人。(结束本次宏任务、检查还有没有宏任务需要处理)
这个检查的过程是持续进行的,每完成一个任务都会进行一次,而这样的操作就被称为Event Loop
setImmediate与setTimeout的区别
setImmediate
为一次Event Loop
执行完毕后调用。setTimeout
则是通过计算一个延迟时间后进行执行。
如果在主进程中直接执行这两个操作,很难保证哪个会先触发。
当注册这两个任务耗时超过delay(s)
,定时器处于可执行回调的状态,会先执行定时器,
执行完定时器以后才是结束了一次Event Loop
,这时才会执行setImmediate
。
JS延迟加载的方式
JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
1.把JS放在页面的最底部(css放顶部,js放底部是框架常见优化)
2.script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
4.动态创建script标签,监听dom加载完毕再引入js文件