前端JavaScript总结

157 阅读11分钟

函数节流和防抖

1. 节流

只在开始执行一次,未执行完成过程中触发的忽略,核心在于开关锁🔒。

例如:多次点击按钮提交表单, (第一次有效)

2. 防抖

只执行最后一个被触发的,清除之前的异步任务,核心在于清零。

例如: 页面滚动处理事件,搜索框输入联想, (最后一次有效)

new操作符具体干了什么

  1. 创建一个空对象

  2. 继承了该函数的原型

  3. 属性和方法被加入到 this 引用的对象中

  4. 新创建的对象由 this 所引用,并且最后隐式的返回 this

严格模式的限制

  1. 变量必须声明后再使用

  2. 函数的参数不能有同名属性,否则报错

  3. 不能使用with语句

  4. 不能对只读属性赋值,否则报错

  5. 不能使用前缀0表示八进制数,否则报错

  6. 不能删除不可删除的属性,否则报错

  7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]

  8. eval不会在它的外层作用域引入变量

  9. eval和arguments不能被重新赋值

  10. arguments不会自动反映函数参数的变化

  11. 不能使用arguments.callee

  12. 不能使用arguments.caller

  13. 禁止this指向全局对象

  14. 不能使用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的指向:

  1. 使用 ES6 的箭头函数
  2. 在函数内部使用 _this = this
  3. 使用 apply、call、bind
  4. new 实例化一个对象

Promise对象

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

链式操作相对于回调函数体验更好。

  1.  promise 构造函数是同步执行的.then是异步的
  2.  promise 状态一旦改变则不能再变
  3.  .then 或者 .catch 都会返回一个新的 promise
  4.  promise 的 .then 或者 .catch 可以被调用多次
  5.  .then 或者 .catch 中 return 一个 error 对象并不会抛出错误
  6. .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()

  • 手写循环递归

区别

浅拷贝和深拷贝都创建出一个新的对象,但在复制对象属性的时候,行为就不一样

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

闭包

指的是能够访问另一个函数作用域的变量的函数。

闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量

  1. 形成: 函数中嵌套函数
  2. 作用: 函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
  3. 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
  4. 缺点: 无法回收闭包中引用变量,容易造成内存泄漏

闭包的应用 

  1. ajax请求的成功回调
  2. 事件绑定的回调方法
  3. setTimeout的延时回调
  4. 函数内部返回另一个匿名函数

以下几个应用场景:

  1. 构造函数的私有属性
  2. 计算缓存
  3. 函数节流、防抖

原型和原型链

原型

定义:给其它对象提供共享属性的对象,简称为原型( 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对象即被终止,作用域链向下访问变量是不被允许的。

箭头函数与普通函数的区别

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数

异步编程的实现方式

  1. 回调函数
  2. 事件监听(采用时间驱动模式,取决于某个事件是否发生)
  3. 发布/订阅(观察者模式)
  4. Generator函数
  5. 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 如何返回另一个函数 barbaz 现在持有对 foo 中定义的bar 函数的引用。由于闭包特性,a的值能够得到