函数节流和防抖
1. 节流
只在开始执行一次,未执行完成过程中触发的忽略,核心在于开关锁🔒。
例如:多次点击按钮提交表单, (第一次有效)
2. 防抖
只执行最后一个被触发的,清除之前的异步任务,核心在于清零。
例如: 页面滚动处理事件,搜索框输入联想, (最后一次有效)
new操作符具体干了什么
-
创建一个空对象
-
继承了该函数的原型
-
属性和方法被加入到 this 引用的对象中
-
新创建的对象由 this 所引用,并且最后隐式的返回 this
严格模式的限制
-
变量必须声明后再使用
-
函数的参数不能有同名属性,否则报错
-
不能使用with语句
-
不能对只读属性赋值,否则报错
-
不能使用前缀0表示八进制数,否则报错
-
不能删除不可删除的属性,否则报错
-
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
-
eval不会在它的外层作用域引入变量
-
eval和arguments不能被重新赋值
-
arguments不会自动反映函数参数的变化
-
不能使用arguments.callee
-
不能使用arguments.caller
-
禁止this指向全局对象
-
不能使用fn.caller和fn.arguments获取函数调用的堆栈
call apply 和 bind的区别
call apply 共同点:
调用 call 和 apply 的对象,必须是一个函数 Function
call: Function.call(obj, param1,,param2,…,paramN)
apply: Function.apply(obj, [argArray])
bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。
并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
call 和 apply 的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别。
bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。
eval 和 with
eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。
看起来它们能实现更复杂的功能,并且代码更具有扩展性。
eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。
换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
with 可以改变作用域,通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
this
this的指向:
ES5中: this 永远指向最后调用它的那个对象
ES6: 箭头函数 的 this 始终指向函数定义时的 this,而非执行时
怎么改变this的指向:
- 使用 ES6 的箭头函数
- 在函数内部使用 _this = this
- 使用 apply、call、bind
- new 实例化一个对象
Promise对象
Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。
链式操作相对于回调函数体验更好。
- promise 构造函数是同步执行的.then是异步的
- promise 状态一旦改变则不能再变
- .then 或者 .catch 都会返回一个新的 promise
- promise 的 .then 或者 .catch 可以被调用多次
- .then 或者 .catch 中 return 一个 error 对象并不会抛出错误
- .then 或 .catch 返回的值不能是 promise 本身
缺点:
无法取消Promise,一旦新建它就会立即执行,无法中途取消。
如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
深浅拷贝
浅拷贝
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
在JavaScript中,存在浅拷贝的现象有:
- Object.assign
- Array.prototype.slice(), Array.prototype.concat()
- 使用拓展运算符实现的复制
深拷贝
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
-
_.cloneDeep()
-
jQuery.extend()
-
JSON.stringify()
-
手写循环递归
区别
浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样
浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
闭包
指的是能够访问另一个函数作用域的变量的函数。
闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量
- 形成: 函数中嵌套函数
- 作用: 函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
- 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
- 缺点: 无法回收闭包中引用变量,容易造成内存泄漏
闭包的应用
- ajax请求的成功回调
- 事件绑定的回调方法
- setTimeout的延时回调
- 函数内部返回另一个匿名函数
以下几个应用场景:
- 构造函数的私有属性
- 计算缓存
- 函数节流、防抖
原型和原型链
原型
定义:给其它对象提供共享属性的对象,简称为原型( prototype )。
JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
下面举个例子:
函数可以有属性。 每个函数都有一个特殊的属性叫作原型prototype
function doSomething(){}
console.log( doSomething.prototype );
//控制台输出
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
function Person(name) {
this.name = name;
this.age = 18;
this.sayName = function() {
console.log(this.name);
}
}
// 第二步 创建实例
var person = new Person('person')
Javascript字符串的常用方法
增
这里增的意思并不是说直接增添内容,而是创建字符串的一个副本,再进行操作
除了常用+以及${}进行字符串拼接之外,还可通过concat
concat()
let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
删
这里的删的意思并不是说删除原字符串的内容,而是创建字符串的一个副本,再进行操作
常见的有:
- slice()
- substr()
- substring()
改
这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作
常见的有:
-
trim()、trimLeft()、trimRight()
-
repeat()
-
padStart()、padEnd()
-
toLowerCase()、 toUpperCase()
查
除了通过索引的方式获取字符串的值,还可通过:
-
chatAt()
-
indexOf()
-
startWith()
-
includes()
Javscript数组的常用方法
增
下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
- push()
- unshift()
- splice()
- concat()
删
下面三种都会影响原数组,最后一项不影响原数组:
- pop()
- shift()
- splice()
- slice()
改
即修改原来数组的内容,常用splice
splice()
传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响
查
即查找元素,返回元素坐标或者元素值
- indexOf()
- includes()
- find()
作用域和作用域链
作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。作用域是一套规则,在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
箭头函数与普通函数的区别
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
异步编程的实现方式
- 回调函数
- 事件监听(采用时间驱动模式,取决于某个事件是否发生)
- 发布/订阅(观察者模式)
- Generator函数
- async函数
说说你对事件循环的理解
JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事
为什么要这么设计,跟JavaScript的应用场景有关
JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)
事件循环(Event Loop)
在JavaScript中,所有的任务都可以分为
-
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
-
异步任务:异步执行的任务,比如
ajax网络请求,setTimeout定时函数等
Javascript中如何实现函数缓存?函数缓存有哪些应用场景
介绍
函数缓存,就是将函数运算过的结果进行缓存
本质上就是用空间(缓存存储)换时间(计算过程)
常用于缓存数据计算结果和缓存对象
实现方式
实现函数缓存主要依靠闭包、柯里化、高阶函数:
1. 闭包
(function() {
var a = 1;
function add() {
const b = 2
let sum = b + a
console.log(sum); // 3
}
add()
})()
add 函数本身,以及其内部可访问的变量,即 a = 1 ,这两个组合在⼀起就形成了闭包
2.柯里化
把接受多个参数的函数转换成接受一个单一参数的函数
// 非函数柯里化
var add = function (x,y) {
return x+y;
}
add(3,4) //7
// 函数柯里化
var add2 = function (x) {
//**返回函数**
return function (y) {
return x+y;
}
}
add2(3)(4) //7
将一个二元函数拆分成两个一元函数
3.高阶函数
通过接收其他函数作为参数或返回其他函数的函数
function foo(){
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2
函数 foo 如何返回另一个函数 bar,baz 现在持有对 foo 中定义的bar 函数的引用。由于闭包特性,a的值能够得到