实战踩坑

1.pc端访问,输入移动端域名重定向到对应的pc版

什么是闭包? 闭包是有权限访问其它函数作用域内的变量的一个函数。

返回目录

1. 关于闭包

什么是闭包? 闭包是有权限访问其它函数作用域内的变量的一个函数。

在 js 中,变量分为全局变量和局部变量,局部变量的作用域属于函数作用域,在函数执行完以后作用域就会被销毁,内存也会被回收,但是由于闭包是建立在函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会被销毁,此时的子函数——也就是闭包,便拥有了访问上级作用域中变量的权限,即使上级函数执行完以后作用域内的值也不会被销毁。

闭包解决了什么? 本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。由于闭包可以缓存上级作用域,这样函数外部就可以访问到函数内部的变量。

闭包的应用场景 ajax 请求成功的回调 一个事件绑定的回调方法 setTimeout 的延时回调 一个函数内部返回另一个匿名函数

闭包的优缺点 优点:让代码更加规范、简介 缺点:使用闭包过多,内存消耗大,造成内存的泄露

2. 原型和原型链

(1).所有的引用类型都有一个*proto*(隐式原型)属性,属性值是一个普通的对象 (2).所有的函数除了有*proto*属性,还都有一个 prototype(显式原型)属性,属性值是一个普通的对象 (3).所有引用类型的*proto*属性指向它构造函数的 prototype

当一个对象调用自身不存在的属性/方法时,会先去它的*proto*上查找,也就是它的构造函数的 prototype;如果没有找到,就会去该构造函数的 prototype 的*proto*指向的上一级函数的 prototype 中查找,再往上会指向对象的 prototype,最后指向 null。这样一层一层向上查找的关系会形成一个链式结构,称为原型链。

深入理解 JS 的原型和原型链

3. ES5 继承和 ES6 继承

ES5:组合式继承,先创建子类的实例对象,然后再将父类的方法通过 call 方法添加到 this 上,通过原型和构造函数的机制来实现

示例:

// 定义一个父类
function Parent() {
  this.name = "爸爸";
  this.age = 50;
  this.sex = "男";
}
// 在父类的原型上添加一个方法
Parent.prototype.play = function () {
  console.log("去打麻将");
};

// 定义一个子类
function Child(name) {
  this.name = name;
  // 第一步:继承父类的属性
  Parent.call(this);
}
// 第二步:实现子类继承父类的方法(儿子从爸爸那里学会了打麻将)
Child.prototype = new Parent();
// 第三步:找回丢失的构造函数
Child.prototype.constructor = Child;

ES6:先创建父类的实例对象 this(所以必须先调用父类的 super()方法,然后在用子类的构造函数修改 this),通过 class 关键字定义类,类之间通过 extends 关键字实现继承,子类必须在 constructor 方法中调用 super 方法。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其加工,如果不调用 super 方法,子类得不到 this 对象。

示例:

// 定义一个父类
class Parent {
  constructor(name, sex) {
    this.name = name;
    this.sex = sex;
  }
  play() {
    console.log(this.name + "打麻将超级垃圾");
  }
  speak() {
    console.log("性别是:" + this.sex);
  }
}
// 父类的实例化
let father = new Parent("老高", "男");
father.play();
// 打麻将超级垃圾
father.speak();
// 性别是男

// 创建一个子类去继承父类
class Child extends Parent {
  constructor(name, sex) {
    // 调用父类的 constructor
    super(name, sex);
  }
}
// 子类的实例化
let son = new Child("小高", "女");
son.play();
son.speak();

4. 原生 AJAX 请求步骤

五步使用法:

  1. 创建 XMLHTTPRequest 对象
  2. 使用 open 方法设置和服务器的交互信息
  3. 设置发送的数据,开始和服务器端交互
  4. 注册事件
  5. 更新界面

Get 请求:

// 第一步:创建异步对象
let xhr = new XMLHttpRequest();
// 第二步:设置请求的url参数,参数1是请求的类型,参数2是请求的url,可以携带参数
xhr.open("get", "/baidu.com?username=1");
// 第三步:设置发送的数据,开始和服务端交互
xhr.send();
// 第四步:注册事件onreadystatechange,当状态改变时会调用
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 第五步:如果到达这一步,说明数据返回,请求的页面是存在的
    console.log(xhr.responseText);
  }
};

POST 请求:

// 第一步:创建异步对象
let xhr = new XMLHttpRequest();
// post请求一定要添加请求头,不然会报错
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 第二步:设置请求的url参数,参数1是请求的类型,参数2是请求的url,可以携带参数
xhr.open("post", "/baidu.com");
// 第三步:设置发送的数据,开始和服务端交互
xhr.send("username=1&password=123");
// 第四步:注册事件onreadystatechange,当状态改变时会调用
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 第五步:如果到达这一步,说明数据返回,请求的页面是存在的
    console.log(xhr.responseText);
  }
};

5. 关于事件委托

什么是事件委托 事件委托也叫事件代理,就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

事件委托的作用 (1).提高性能:每一个函数都会占用内存空间,只需添加一个时间处理程序代理所有事件,所占用的内存空间更少;(2).动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以具有和其它元素一样的事件。

实现方式 我们先来看看,如果不用事件委托,需要绑定多个相同事件的时候是如何实现的:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>
      Title
    </title>
  </head>
  <body>
    <ul>
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
    </ul>
  </body>
  <script>
    window.onload = function () {
      let lis = document.getElementsByClassName("item");
      for (let i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
          console.log("用力的点我");
        };
      }
    };
  </script>
</html>

不使用事件委托,那就要遍历每一个 li 元素,给每个 li 元素绑定一个点击事件,这样的做法非常耗费内存,如果有 100 个、1000 个 li 元素,那对性能的影响是非常大的。

那么使用事件委托是怎么实现的呢?

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>
      Title
    </title>
  </head>
  <body>
    <ul id="wrap">
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
      <li class="item">
        按钮
      </li>
    </ul>
  </body>
  <script>
    window.onload = function () {
      let ul = document.getElementById("wrap");
      ul.onclick = function (ev) {
        // 获取到事件对象
        let e = ev || window.event;
        // 如果点击的元素的calssName为item
        if (e.target.className === "item") {
          console.log("用力的点我");
        }
      };
    };
  </script>
</html>

这样一来,通过事件委托,只需要在 li 元素的父元素 ul 上绑定一个点击事件,通过事件冒泡的机制,就可以实现 li 的点击效果。并且通过 js 动态添加 li 元素,也能绑定点击事件。

6. null 不是一个对象,但为什么 typeof null === object

原理是这样的,不同的对象在底层都会表示为二进制,在 js 中如果二进制的前三位都为 0,就会被判断为 object 类型,null 的二进制全为 0,自然前三位也是 0,所以 typeof null === objcet。

7. 关于深拷贝和浅拷贝

浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

实现:

方法 1:直接用=赋值

let obj1 = { a: 1 };
let obj2 = obj1;

方法 2:Object.assign

let obj1 = { a: 1 };
let obj2 = {};
Object.assign(obj2, obj1);

方法 3:for in 循环只遍历第一层

function shallowObj(obj) {
  let result = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = obj[key];
    }
  }
  return;
  result;
}
let obj1 = {
  a: 1,
  b: {
    c: 2,
  },
};
let obj2 = shallowObj(obj1);
obj1.b.c = 3;
console.log(obj2.b.c);

// 3
深拷贝: 方法 1:用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象

let obj1 = {
  a: 1,
  b: 2,
};
let obj2 = JSON.parse(JSON.stringify(obj1));

方法 2:采用递归去拷贝所有层级属性

function deepClone(obj) {
  // 如果传入的值不是一个对象,就不执行
  if (Object.prototype.toString.call(obj) !== "[object Object]") return;
  // 根据传入值的类型初始化返回结果
  let result = obj instanceof Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 如果obj是个对象,就递归调用deepClone去遍历obj的每一层属性,如果不是对象就直接返回值
      result[key] =
        Object.prototype.toString.call(obj[key]) === "[object Object]"
          ? deepClone(obj[key])
          : obj[key];
    }
  }
  return;
  result;
}
// 改进判断对象的方法
console.log(typeof null === "object");
// true
console.log(Object.prototype.toString.call(null) === "[object Object]");
// false

方法 3:lodash 函数库实现深拷贝

let obj1 = {
  a: 1,
  b: 2,
};
let obj2 = _.cloneDeep(obj1);

方法 4:通过 jQuery 的 extend 方法实现深拷贝

let array = [1, 2, 3, 4];
let newArray = $.extend(true, [], array);
// true为深拷贝,false为浅拷贝

方法 5:用 slice 实现对数组的深拷贝

let arr1 = ["1", "2", "3"];
let arr2 = arr1.slice(0);
arr2[1] = "9";
console.log(arr2);
// ['1', '9', '3']
console.log(arr1);
// ['1', '2', '3']

方法 6:使用扩展运算符实现深拷贝

let obj1 = { brand: "BMW", price: "380000", length: "5米" };
let obj2 = { ...car, price: "500000" };

js 浅拷贝与深拷贝的区别和实现方式

8. 谈谈 js 的垃圾回收机制

js 拥有自动的垃圾回收机制,当一个值在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

标记清除法(常用) (1).标记阶段:垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象;(2).清除阶段:垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作;

优点:实现简单 缺点:可能会造成大量的内存碎片

引用计数清除法 (1).引用计数的含义就是跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型赋值给该变量时,这个值的引用次数就是 1。相反,如果包含对这个值引用的变量又取得了另外一个值,这个值的引用次数就减 1。(2).当这个引用次数变成 0 时,则说明没有办法再访问这个值了,就可以将其所占的内存空间给回收。这样,垃圾收集器下次再运行时,就会释放那些引用次数为 0 的值所占的内存。

优点:(1).可即刻回收垃圾

缺点:(1).计数器值的增减处理繁重 (2).实现繁琐复杂 (3).循环引用无法回收

9. 如何阻止事件冒泡和默认事件

标准的 DOM 对象中可以使用事件对象的 stopPropagation()方法来阻止事件冒泡,但在 IE8 以下中的事件对象通过设置事件对象的 cancelBubble 属性为 true 来阻止冒泡

默认事件通过事件对象的 preventDefault()方法来阻止,而 IE 通过设置事件对象的 returnValue 属性为 false 来阻止默认事件

10. 函数去抖和函数节流

函数去抖(debounce): 当调用函数 n 秒后,才会执行该动作,若在这 n 秒内又调用该函数则取消前一次并重新计算执行时间(频繁触发的情况下,只有足够的空闲时间,才执行代码一次)

function debounce(delay, cb) {
  let timer;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(function () {
      cb();
    }, delay);
  };
}

JavaScript 专题之跟着 underscore 学防抖

函数节流(throttle): 函数节流的基本思想是函数预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期(一定时间内 js 方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次)

function throttle(cb, delay) {
  let startTime = Date.now();
  return function () {
    let currTime = Date.now();
    if (currTime - startTime > delay) {
      cb();
      startTime = currTime;
    }
  };
}

JavaScript 专题之跟着 underscore 学节流

11. 谈谈 js 的事件循环机制

程序开始执行之后,主程序则开始执行同步任务,碰到异步任务就把它放到任务队列之中,等到同步任务全部执行完后,js 引擎便去查看任务队列有没有可以执行的异步任务,将异步任务转成同步任务并开始执行,执行完同步任务后继续查看任务队列。这个过程是一直循环的,因此这个过程就是所谓的事件循环,其中任务队列也被称为事件队列。通过一个任务队列,单线程的 js 实现了异步任务的执行,给人的感觉好像是多线程的。

12. 箭头函数和普通函数的区别

普通函数:(1).this 总是代表它的直接调用者 (2).在默认情况下,没找到直接调用者,this 指向 window (3).在严格模式下,没有直接调用者的函数中的 this 是 undefined (4).使用 call,apply,bind 绑定,this 指的是绑定的对象

箭头函数:(1).在使用 => 定义函数的时候,this 的指向是定义时所在的对象,而不是使用时所在的对象,bind()、call()、apply()均无法改变指向 (2).不能用做构造函数,也就是说不能使用 new 命令,否则就会抛出一个错误 (3).不能使用 arguments 对象,但是可以使用…rest 参数 (4).不能使用 yield 命令 (5).没有原型属性

13. call()、apply()、bind()的区别

call()、apply()、bind()是用来改变 this 的指向的

call(): Function.call(obj, param1,param2,param3) 接收到的是 param1,param2,param3 三个参数

apply(): Function.apply(obj, [param1,param2,param3]) 接收到的是 param1,param2,param3 三个参数

call 和 apply 的区别是参数一个不用[],一个要用[]

bind(): const newFn = Funtion.bind(obj, param1,param2) 返回值是一个函数,需要()来调用 newFn(param3,param4) 接收到的是 param1,param2,param3,param4 四个参数

14. 实现一个 sleep 函数

js 不像 java 一样有 sleep()方法,但由于 js 是单线程的,可以利用伪死循环阻塞主线程来达到延迟执行的效果

function sleep(delay) {
  // 获取一个初始时间
  let startTime = new Date().getTime();
  // 如果时间差小于延迟时间,就一直循环
  while (new Date().getTime() - startTime < delay) {
    continue;
  }
}

15. 进程和线程的区别

进程(process):是 cpu 资源分配的最小单位(是能拥有资源和独立运行的最小单位),是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念。

线程(thread):是 cpu 调度的最小单位(是建立在进程基础上的一次程序运行单位),是进程内可调度的实体,比进程更小的独立运行的基本单位。

一个进程有一个或多个线程,线程之间共同完成进程分配下来的任务,打个比方:● 假如进程是一个工厂,工厂有它的独立的资源 ● 工厂之间相互独立 ● 线程是工厂中的工人,多个工人协作完成任务 ● 工厂内有一个或多个工人 ● 工人之间共享空间

再完善完善概念:● 工厂的资源 -> 系统分配的内存(独立的一块内存) ● 工厂之间的相互独立 -> 进程之间相互独立 ● 多个工人协作完成任务 -> 多个线程在进程中协作完成任务 ● 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成 ● 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

浅谈浏览器多进程与 JS 线程

16. ES6、ES7、ES8 的新特性

ES6 的特性

(1). 类(class) 对熟悉 Java、C、C++等语言的开发者来说,class 一点都不陌生。ES6 引入了 class(类),让 JS 的面向对象编程变得更加简单和易于理解。

(2).模块化(Module) ES5 不支持原生的模块化,在 ES6 中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过 import 来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

导出(export) ES6 运行在一个模块中使用 export 来导出多个变量或函数

// 导出变量
export let name = "gg";

// 导出常量
export const name = "gg";

// 导出多个变量
let a = 2;
let b = 4;
export { a, b };

// 导出函数
export function myModule(someArg) {
  return someArg;
}

导入(import) 定义好模块的输出以后就可以在另外一个模块通过 import 引用。

import { myModule } from "myModule";
import { a, b } from "test";

(3).箭头(Arrow)函数 这是 ES6 中最令人激动的特性之一。=>不只是关键字 function 的简写,它还带来了其它好处。箭头函数与包围它的代码共享同一个 this,能很好的解决 this 的指向问题。

(4).函数参数默认值 ES6 支持在定义函数的时候为其设置默认值,当函数的参数为布尔值 false 时,可以规避一些问题


// 使用默认值
functionfoo(height =50, color ='red') {
//
}

// 不使用默认值
function foo(height, color) {
let height = height ||50
let color = color ||'red'
}

(5).模板字符串

// 不使用模板字符串
let name = "Your name is " + first + " " + last + ".";

// 使用模板字符串
let name = `Your name is ${first} ${last}.`;

(6).解构赋值 通过解构赋值可以方便的交换两个变量的值:

let a = 1;
let b = ((3)[(a, b)] = [b, a]);
console.log(a);
// 3
console.log(b);
// 1

获取对象中的值:

const student = {
  name: "Ming",
  age: "18",
  city: "Shanghai",
};

const { name, age, city } = student;
console.log(name);
// "Ming"
console.log(age);
// "18"
console.log(city);
// "Shanghai"

(7).延展操作符(Spread operator)和剩余运算符(rest operator) 当三个点(...)在等号右边,或者放在实参上,是 spread 运算符

myFunction(...arr);

let arr1 = [1, 2, 3, 4];
let arr2 = [...arr1, 4, 5, 6];
console.log(arr2);
// [1, 2, 3, 4, 4, 5, 6]

当三个点(...)在等号左边,或者放在形参上,是 rest 运算符

function myFunction(...arr) {}

let [a, ...temp] = [1, 2, 4];
console.log(a);
// 1
console.log(temp);
// [2, 4]

(8).对象属性简写 在 ES6 中允许我们在设置一个对象的属性的时候不指定属性名

不使用 ES6:

const name = "Ming",
  age = "18",
  city = "Shanghai";
const student = {
  name: name,
  age: age,
  city: city,
};
console.log(student);
//{name: "Ming", age: "18", city: "Shanghai"}

使用 ES6:

const name = "Ming",
  age = "18",
  city = "Shanghai";

const student = {
  name,
  age,
  city,
};
console.log(student);
//{name: "Ming", age: "18", city: "Shanghai"}

(9).Promise Promise 是异步编程的一种解决方案,比传统的解决方案 callback 更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。

不使用 ES6

setTimeout(function () {
  console.log("Hello");
  // 1秒后输出"Hello"
  setTimeout(function () {
    console.log("Hi");
    // 2秒后输出"Hi"
  }, 1000);
}, 1000);

使用 ES6

let waitSecond = new Promise(function (resolve, reject) {
  setTimeout(resolve, 1000);
});

waitSecond
  .then(function () {
    console.log("Hello");
    // 1秒后输出"Hello"
    return;
    waitSecond;
  })
  .then(function () {
    console.log("Hi");
    // 2秒后输出"Hi"
  });

(10).支持 let 与 const 在之前 JS 是没有块级作用域的,const 与 let 填补了这方便的空白,const 与 let 都是块级作用域。

let 和 var 的区别:● let 没有变量提升,存在暂时性死区,必须等 let 声明完以后,变量才能使用 ● let 变量不能重复声明 ● let 声明的变量只在 let 代码块有效

ES7 的特性

(1).Array.prototype.includes() includes() 函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回 false

let arr = ["react", "angular", "vue"];
if (arr.includes("react")) {
  console.log("react存在");
}

(2).指数操作符 在 ES7 中引入了指数运算符,具有与 Math.pow(..)等效的计算结果。

console.log(Math.pow(2, 10));
// 输出1024

console.log(2 ** 10);
// 输出1024

ES8 的特性

(1).async/await 在 ES8 中加入了对 async/await 的支持,也就我们所说的异步函数,这是一个很实用的功能。async/await 相当于一个语法糖,解决了回调地狱的问题

(2).Object.values() Object.values()是一个与 Object.keys()类似的新函数,但返回的是 Object 自身属性的所有值,不包括继承的值。


const
 obj = {a:1,b:2,c:3,}
// 不使用Object.values()
const vals =Object.keys(obj).map(e => obj[e])
console.log(vals)
// [ 1, 2, 3 ]

// 使用Object.values()
console.log(Object.values(obj))
// [ 1, 2, 3 ]
(3).Object.entries Object.entries()函数返回一个给定对象自身可枚举属性的键值对的数组。

const
 obj = {a:1,b:2,c:3,}
// 不使用Object.entries()
Object.keys(obj).forEach(key=>{
    console.log('key:'+key+'value:'+obj[key])
})
//key:a value:1
//key:b value:2
//key:c value:3

// 使用Object.entries()
for(let[key,value] of Object.entries(obj1)){
    console.log(`key: ${key} value:${value}`)
}
//key:a value:1
//key:b value:2
//key:c value:3

(4).String padding 在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾

String.padStart(targetLength,[padString])
targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。padString:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为 " "。

console.log("0.0".padStart(4, "10"));
//10.0
console.log("0.0".padStart(20));
//                0.00

String.padEnd(targetLength,padString])
targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。padString:(可选) 填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为 " ";

console.log("0.0".padEnd(4, "0"));
//0.00
console.log("0.0".padEnd(10, "0"));
//0.00000000

(5).函数参数列表结尾允许逗号

// 不使用ES8
//程序员A
let f = function(a,
                 b
) {
...
}

//程序员B
let f = function(a,
                 b,   //变更行
                 c   //变更行
) {
...
}

//程序员C
let f = function(a,
                 b,
                 c,   //变更行
                 d   //变更行
) {
...
}

// 使用ES8
//程序员A
let f = function(a,
                 b,
) {
...
}

//程序员B
let f = function(a,
                 b,
                 c,   //变更行
) {
...
}

//程序员C
let f = function(a,
                 b,
                 c,
                 d,   //变更行
) {
...
}

(6). Object.getOwnPropertyDescriptors() Object.getOwnPropertyDescriptors()函数用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。

const obj2 = {
  name: "Jine",
  get age() {
    return;
    ("18");
  },
};
Object.getOwnPropertyDescriptors(obj2);
// {
//   age: {
//     configurable: true,
//     enumerable: true,
//     get: function age(){}, //the getter function
//     set: undefined
//   },
//   name: {
//     configurable: true,
//     enumerable: true,
//        value:"Jine",
//        writable:true
//   }
// }

ES6、ES7、ES8 特性一锅炖(ES6、ES7、ES8 学习指南)

17. 性能监控平台是如何捕获错误的

全局捕获 通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有:(1).window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等 (2).框架级别的全局监听,例如 aixos 中使用 interceptor 进行拦截,vue、react 都有自己的错误采集接口 (3).通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常 (4).对实例方法重写(Patch),在原有功能基础上包裹一层,例如对 console.error 进行重写,在使用方法不变的情况下也可以异常捕获

单点捕获 在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:(1).try…catch 中 throw err (2).专门写一个函数来收集异常信息,在异常发生时,调用该函数 (3).专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行结果和原函数一模一样,只是在发生异常时可以捕获异常

前端异常监控解决方案研究

18. 函数柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术

示例:

function add(a, b) {
  return;
  a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2);
// 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2);
// 3

addCurry(1)(2) = add(1, 2);

实现:(1).最简单的方式,使用 lodash 库的\_.curry

function sum(a, b) {
  return;
  a + b;
}

// 使用来自 lodash 库的 _.curry
let curriedSum = _.curry(sum);

alert(curriedSum(1, 2));
// 3
alert(curriedSum(1)(2));
// 3

(2).自定义函数实现


function
 curry(func) {
return
function
 curried(...args) {
if
(args.length >= func.length) {
return
 func.apply(
this
, args)
}
else
{
return
function
(...args2) {
return
 curried.apply(
this
, args.concat(args2))
}
}
}
}

let
 currfn = curry(add)
console.log(currfn(
1
)(
3
))
// 4

JavaScript 专题之函数柯里化

19. new 关键字做了什么?

使用 new 操作符调用构造函数实际上会经历以下 4 个步骤:

  • (1).创建一个新对象
  • (2).将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  • (3).执行构造函数中的代码(为这个新对象添加属性、方法)
  • (4).返回新对象
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

function myNew() {
  // 创建一个新对象
  var obj = {};
  // 拿到构造函数,因为构造函数是第一个参数,arguments里面的其余的参数就是构造函数的参数
  var constructorFunction = [].shift.call(arguments);
  // 此时的arguments已经是去掉了第一个参数的arguments
  var params = arguments;
  // 这句是改变constructorFunction的this指向到obj并且执行constructorFunction这个函数
  var res = constructorFunction.apply(obj, params);
  //  res是否 Object 实例的对象的方法
  return res instanceof Object ? res : obj;
}
1-19 参考文章:https://juejin.im/post/5e93deb46fb9a03c77620c13

20. 根据访问设备重定向

Nignx 配置

# 1.pc端访问,输入移动端域名重定向到对应的pc版

if ($http_user_agent !~* "^(.*android.*)|(.*iphone.*)|(.*windows phone.*)$") {
rewrite ^ http://pc.domain.com redirect;
break;

}

# 2.移动端访问,输入pc端域名重定向到对应移动版

if ($http_user_agent ~* "^(.*android.*)|(.*iphone.*)|(.*windows phone.*)$") {
rewrite ^ http://m.domain.com redirect;
break;

}

前端配置

<script>
  const isMobile = /iphone|ipod|ipad|android|blackberry|opera|mini|smartphone|iemobile|windows\sphone/i.test(
    navigator.userAgent.toLowerCase()
  );
  if (isMobile) {
    const isProdEnv = location.hostname === "www.17golink.xyz";
    const mobileProd = "http://h5.17golink.xyz";
    const mobileTest = "http://test-c-circle.ngchat.cn";
    const isSharePage = location.pathname === "/counter/share";
    let fullPath = isSharePage ? location.pathname + location.search : "";
    location.replace(isProdEnv ? mobileProd + fullPath : mobileTest + fullPath);
  }
</script>