前端面试题(七)(JS篇)建议收藏,持续更新中...

121 篇文章 7 订阅
8 篇文章 0 订阅

请使用js实现一个秒表计时器的程序

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>计时器</title>
</head>

<body>
    <div id="counter"></div>
</body>

</html>
<script>
    /* 选中所需要的couter */
    const ele = document.getElementById('counter');
    /* 定义两个变量  */
    let intervalId, seconds;
    /* 定义一个计算时间的方法 */
    function counter() {
        /* 定义定时器  */
        intervalId = window.setInterval(() => {
            --seconds;
            ele.innerText = seconds;
            if (seconds === 0) {
                alert('计时结束');
                window.clearInterval(intervalId);
            }
        }, 1000);
    }

    function stopCounter() {
        window.clearInterval(intervalId);
    }

    function resumeCounter() {
        counter();
    }

    seconds = 100;
    counter();
</script>

 运行结果
在这里插入图片描述

模拟 localStorage 时如何实现过期时间功能

1.存储时记录下有效截止时间
2.取数据时判断是否超过有效时间,在有效期内则返回,不在则提示或返回空并且将其删除
class MyStorage {
  get(key) {
    const wrapValue = localStorage.getItem(key)
    if (wrapValue == null) {
      return undefined
    }
    
    const {value, expires} = JSON.parse(wrapValue)
-    if ((expires != null && Date.now() < expires) || expires == null) {
+    if (!expires || Date.now() < expires) {
      return value
    } 
-
+   localStorage.rmeoveItem(key)
    return undefined
  }

  set(key, value, period) {
    const wrapValue = { value };
    if (period != null) {
      wrapValue.expires = Date.now() + period;
    }

    localStorage.setItem(key, JSON.stringify(wrapValue));
  }
}

请使用js实现商品的自由组合,并说说你的思路

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>商品sku</title>
</head>

<body>

</body>

</html>
<script>
    var a = ['黄', '绿']
    var b = ['s', 'l']
    var c = ['大', '小']

    function decar(...args) {
        return args.reduce((acc, item) => {
            const res = []
            for (let i = 0; i < acc.length; i++) {
                for (let j = 0; j < item.length; j++) {
                    const prev = [].concat(acc[i])
                    res.push([...prev, item[j]])
                }
            }
            return res
        })
    }
    decar(a, b, c)
    console.log(decar(a, b, c))
</script>

运行结果
在这里插入图片描述

js中的undefined和 ReferenceError: xxx is not defined 有什么区别?

undefined是变量已声明,但未赋值
ReferenceError: xxx is not defined 是xxx未声明,使用了不存在的变量

JavaScript Number.toPrecision() 函数详解
JavaScript:

numberObject.toPrecision( [ precision ] )

如果没有提供precision参数或其值为undefined,则将转而调用toString()方法进行处理。
如果提供了参数,则参数precision必须介于 [1, 21] 之间,否则将报错。
如果数字的有效位数大于precision,将会根据第precision + 1位的有效数字进行四舍五入。
返回值

toPrecision()函数的返回值为String类型,返回该数字以指数计数法或定点表示法的表示形式。如果该数字的有效位数小于precision位,则在小数部分填充相应位数的0。

如果参数precision大于等于数字整数部分的位数,则返回值将采用定点表示法;否则将采用指数计数法。

有效数字的位数是从数字第一个不为0的数(包括整数部分和小数部分)开始计算的位数。 


示例&说明

JavaScript:

//toPrecision()会进行四舍五入

var num = 423.536;
// 调用的是toString()方法
document.writeln( num.toPrecision() ); // 423.536

num = 562345.12456;
// 由于整数部分有6位,要求只有3位有效数字,必须采用指数计数法才能表示。
document.writeln( num.toPrecision( 3 ) ); // 5.62e+5

num = -2651.10;
// 整数部分有4位,要求的有效数字为4位,采用定点表示法
document.writeln( num.toPrecision( 4 ) ); // -2651

num = 4564561.12457;
// 整数部分有7位,要求有效数字为1位,采用指数计数法
document.writeln( num.toPrecision( 1 ) ); // 5e+6

num = 231;
// 整数部分有3位,要求有效数字为5位,采用定点表示法,并在小数部分填充两个0
document.writeln( num.toPrecision( 5 ) ); // 231.00



 

获取浏览器当前页面的滚动条高度的兼容写法

document.documentElement.scrollTop || document.body.scrollTop;

一道变态题 Number.call.call(Number, undefined, 0) 等于什么?

call 的第一个参数用于改变上下文,由于没有用到 this,第一个参数 Number 实际上没有用。

    Number.call(Number, undefined, 0) 等价于 Number(undefined, 0),由于 Number 只会接受第一个参数。
    Number.call.call(Number, undefined, 0) 等价于 Number.call(undefined, 0),也就是 Number(0)。

ReferenceError和TypeError有什么区别?

ReferenceError

指的是引用出错,比如尝试访问未定义的变量,或者提前访问无提升的变量,都会引发这个错误:

console.log(foo);  // ReferenceError: foo is not defined
let foo = 1;

TypeError

指的是类型出错。

众所周知JavaScript是一个弱类型的语言,这既是它的优点,也经常被人诟病。你永远也不知道 a + b 是在做数值加减还是字符串拼接,而且似乎 a + b 几乎从来也不会报错。

而tc39自 ES5 到 ES6 以来,将 TypeError 的频率几乎翻了4倍之多。目的就是为了规范和强调类型这一概念,避免过于模糊类型这一概念导致JavaScript对于语义的不确定性。
 

Symbol() + 1; // TypeError: Cannot convert a Symbol value to a number

({ toString: () =>({}) }) + 'a';  // TypeError: Cannot convert object to primitive value

这样使得JavaScript更为规范,当一个方法的参数需要一个 number 但却收到了一个 object,与其让它返回 NaN,不如果断地抛出 TypeError 吧。

如何避免JS浮点运算的精度问题(例:0.1+0.7=0.7999999999999999)
 function precision(num1,num2){
        num1Length = num1.toString().length;
        num2Length = num2.toString().length;
        let len =  num1Length > num2Length ? num1Length : num2Length;
        return (num1 + num2).toPrecision(len);
    }
    console.log(precision(0.1,0.7));

JavaScript Number.toPrecision() 函数详解

JavaScript:

numberObject.toPrecision( [ precision ] )

如果没有提供precision参数或其值为undefined,则将转而调用toString()方法进行处理。
如果提供了参数,则参数precision必须介于 [1, 21] 之间,否则将报错。
如果数字的有效位数大于precision,将会根据第precision + 1位的有效数字进行四舍五入。
返回值

toPrecision()函数的返回值为String类型,返回该数字以指数计数法或定点表示法的表示形式。如果该数字的有效位数小于precision位,则在小数部分填充相应位数的0。

如果参数precision大于等于数字整数部分的位数,则返回值将采用定点表示法;否则将采用指数计数法。
 

有效数字的位数是从数字第一个不为0的数(包括整数部分和小数部分)开始计算的位数。 

示例&说明
 

JavaScript:

//toPrecision()会进行四舍五入

var num = 423.536;
// 调用的是toString()方法
document.writeln( num.toPrecision() ); // 423.536

num = 562345.12456;
// 由于整数部分有6位,要求只有3位有效数字,必须采用指数计数法才能表示。
document.writeln( num.toPrecision( 3 ) ); // 5.62e+5

num = -2651.10;
// 整数部分有4位,要求的有效数字为4位,采用定点表示法
document.writeln( num.toPrecision( 4 ) ); // -2651

num = 4564561.12457;
// 整数部分有7位,要求有效数字为1位,采用指数计数法
document.writeln( num.toPrecision( 1 ) ); // 5e+6

num = 231;
// 整数部分有3位,要求有效数字为5位,采用定点表示法,并在小数部分填充两个0
document.writeln( num.toPrecision( 5 ) ); // 231.00

举例说明js立即执行函数的写法有哪些?

1、(function(){
//code
}())

2、!function(){
//code
}()

3、!(function(){
//code
})()

4、!(()=>{
//code
})()

for in 和 for of 的区别? 

for of 用于遍历于数组和可迭代对象得到的是entity({key: value}), for in 用于遍历对象的得到的是对象的属性名
for in 不可用来遍历一个数组, for in 将会把数组中的 length 等不需要的属性给一并遍历出来
for of 不可用来遍历对象,对象是一个不可迭代对象。

写一个方法判断数组内元素是否全部相同

const isSameArray = function (array) {
  if (Array.isArray(array)) {
    return new Set(array).size === 1;
  }

  return false;
};

说说防止重复发送ajax请求的方法有哪些?各自有什么优缺点? 

// 方法一 防抖

function debounce(f, ms) {
let time;
return function(){
let arg = Array.prototype.slice.call(arguments, 1);
if(time) {
clearTimeout(time);
}
time = setTimeout(function(){
f.apply(this, arg)
},ms)
}
}

// 方法二 节流
function throttle(f, ms){
let lastTime = 0;
return function(){
let arg = Array.prototype.slice.call(arguments, 1);
let newTime = Date.now();
if(newTime-lastTime > ms) {
setTimeout(function(){
f.apply(this, arg)
},ms)
}
lastTime = newTime;
}
}

    防抖法:在一段时间内重复请求,则取消本次请求
    节流法:在一段时间内只能请求一次,下次请求必须在前一次请求完成后
    等值法:未完成请求状态不再请求,而是完成后直接返回相同的内容

 请使用 js 实现一个双向链表

start


链表结构是我们在面试中经常会被问起的较为基础的数据结构问题,起初学习数据结构使用的是C++语言,最近在做前端面试题的过程中没碰到了需要用js实现双链表的需求,百度出来的文章发现可很多错误,于是索性自己重新写了,并且列出了一些错误点,这里给大家较为详细的一步步看一下实现思想和步骤,相较于C++语言,js的实现可以说是很简单了,不需要创建繁琐的对象,更加直观易懂;
首先我们来看一下双向链表的结构图:
在这里插入图片描述
每个结点包含三部分,指向前一个结点的指针(pre),指向后一个节点的指针(next),以及自己的数据部分(element),于是我们就可以先写出结点对象

function Node:定义结点对象

function Node(element) {
  this.element = element
  this.next = null
  this.previous = null
}

然后我们开始实现插入链表的算法
在这里插入图片描述

(声明)下面函数中的this是我们最后初始化链表的实例,这里大家不要疑惑。可以拉到最下面我们初始化链表那里,相信你会明白呦

**`function insert`:插入节点**

function insert(newelement, currentelement) {
  var newNode = new Node(newelement)
  var currentNode = this.find(currentelement)
  if (currentNode === 'error') {
    console.log('无法插入,要插入节点不存在')
    return
  }
  if (currentNode.next != null) {
    newNode.next = currentNode.next
    currentNode.next = newNode
    newNode.previous = currentNode
    newNode.next.previous = newNode
  } else {
    currentNode.next = newNode
    newNode.previous = currentNode
  }
}

function find:找到插入位置 

function find(element) {
  var currentNode = this.head
  while (currentNode.element != element) {
  /*如果找到最后一个节点还没有找到我们的插入点,那么我们就会返回错误*/
    if (currentNode.next == null) {
      console.log('can not find this node; maybe not have this node')
      return 'error'
    }
    currentNode = currentNode.next
  }
  return currentNode
}

接下来是移除结点的实现,如果看懂了插入节点的实现,那么移除就会很简单了,相信大家都可以很快明白,这里就直接贴出实现代码; 

function remove:移除一个结点

function remove(element) {
  var currentNode = this.find(element)
  if (currentNode === 'error') {
    console.log('要移除节点不存在')
    return
  }
  /*首先是不是头尾节点的情况*/

  if (currentNode.next != null && currentNode.previous != null) {
    currentNode.previous.next = currentNode.next
    currentNode.next.previous = currentNode.previous
    currentNode.next = null
    currentNode.previous = null
  } else if (currentNode.previous == null) {
    /*当是头节点的时候*/
    this.head = currentNode.next
    currentNode.next.previous = null
    currentNode.next = null
  } else if (currentNode.next == null) {
    /*当是尾节点的时候 */

    currentNode.previous.next = null
    currentNode.previous = null
  }
}

截止到这里我们基本功能已经有了,下面使我们根据自己需要可以自定义一些其他函数 

function lastNode:找到最后一个节点

function lastNode() {
  var head = this.head
  while (head.next != null) {
    head = head.next
  }
  return head     //这里head在尾节点的位置
}
function append:将要添加的结点放在链表末尾

function append(element) {
  var lastnode = this.lastNode()
  var newNode = new Node(element)
  lastnode.next = newNode
  newNode.previous = lastnode
}
function showlist:将链表所有的结点打印出来

function showlist() {
  var head = this.head
  do {
    console.log(head.element)
    head = head.next
  } while (head != null)
  // 大家可以看一下下面注释内容存在什么问题,留给大家思考一下
  // while (head.next != null) {
  //   console.log(head.element)
  //   head = head.next
  // }
}

接下来是对链表进行初始化,这也是上述函数中所有this所代表的实例

function initlist:初始化链表,并将所有方法注册到链表中

function initlist() {
  this.head = new Node('one')
  this.find = find
  this.insert = insert
  this.remove = remove
  this.showlist = showlist
  this.lastNode = lastNode
  this.append = append
}

var list = new initlist()
list.insert('two', 'one')
list.insert('four', 'two')
list.insert('three', 'two')

// console.log(list.head.next)
list.showlist()
list.append('A')
list.append('B')
list.insert('B2', 'B')
list.showlist()
console.log(list.lastNode())
// list.remove('one')
// list.showlist()
console.log(list.find('A').previous)
// console.log(list.find('four').previous)
// console.log(list.head.element)

下面是运行结果:
在这里插入图片描述
源码: 

function Node(element) {
  this.element = element
  this.next = null
  this.previous = null
}
function find(element) {
  var currentNode = this.head
  while (currentNode.element != element) {
    if (currentNode.next == null) {
      console.log('can not find this node; maybe not have this node')
      return 'error'
    }
    currentNode = currentNode.next
  }
  return currentNode
}
function insert(newelement, currentelement) {
  var newNode = new Node(newelement)
  var currentNode = this.find(currentelement)
  if (currentNode === 'error') {
    console.log('无法插入,要插入节点不存在')
    return
  }
  if (currentNode.next != null) {
    newNode.next = currentNode.next
    currentNode.next = newNode
    newNode.previous = currentNode
    newNode.next.previous = newNode
  } else {
    currentNode.next = newNode
    newNode.previous = currentNode
  }
}
function remove(element) {
  var currentNode = this.find(element)
  if (currentNode === 'error') {
    console.log('要移除节点不存在')
    return
  }
  /*首先是不是头尾节点的情况*/

  if (currentNode.next != null && currentNode.previous != null) {
    currentNode.previous.next = currentNode.next
    currentNode.next.previous = currentNode.previous
    currentNode.next = null
    currentNode.previous = null
  } else if (currentNode.previous == null) {
    /*当是头节点的时候*/
    this.head = currentNode.next
    currentNode.next.previous = null
    currentNode.next = null
  } else if (currentNode.next == null) {
    /*当是尾节点的时候 */

    currentNode.previous.next = null
    currentNode.previous = null
  }
}
function showlist() {
  var head = this.head
  do {
    console.log(head.element)
    head = head.next
  } while (head != null)
  // while (head.next != null) {
  //   console.log(head.element)
  //   head = head.next
  // }
}
function initlist() {
  this.head = new Node('one')
  this.find = find
  this.insert = insert
  this.remove = remove
  this.showlist = showlist
  this.lastNode = lastNode
  this.append = append
}
function append(element) {
  var lastnode = this.lastNode()
  var newNode = new Node(element)
  lastnode.next = newNode
  newNode.previous = lastnode
}
function lastNode() {
  var head = this.head
  while (head.next != null) {
    head = head.next
  }
  return head
}
var list = new initlist()
list.insert('two', 'one')
list.insert('four', 'two')
list.insert('three', 'two')

// console.log(list.head.next)
list.showlist()
list.append('A')
list.append('B')
list.insert('B2', 'B')
list.showlist()
console.log(list.lastNode())
// list.remove('one')
// list.showlist()
console.log(list.find('A').previous)
// console.log(list.find('four').previous)
// console.log(list.head.element)

 END


 ajax请求地址只支持http/https吗?能做到让它支持rtmp://等其它自定义协议吗 ?

ajax只支持http/https协议,
可以通过自定义http头来间接支持自定义协议

请写一个性能最好的深度克隆对象的方法

const deepClone = (obj) => {
  const copy = obj instance Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
       copy[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
   }
  }
return copy;
}

使用ajax请求真的不安全吗?为什么?

AJAX是发送HTTP请求的一种方式,只不过浏览器给它加了一个同源策略而已。
所以从这一点可以得出一个结论:AJAX本质上安全性和HTTP请求一样

 你有使用过pjax吗?它的原理是什么?

pushState + ajax = pjax

 根据元素ID遍历树形结构,查找到所有父元素ID

var list = [{
"orgId": 1,
"orgName": "校长办公室1",
"parentId": 0
},{
"orgId": 2,
"orgName": "校长办公室2",
"parentId": 1,
},{
"orgId": 3,
"orgName": "校长办公室3",
"parentId": 2,
}];

function findParent(idx){
		list.forEach(item => {
		if (idx === item['orgId']){
						let pid = item['parentId']
						console.log(pid)
						findParent(pid)
		}
	})
}
findParent(3);   //2 1 0

 举例说明Object.defineProperty会在什么情况下造成循环引用导致栈溢出?

var data = {
    count: 1,
    value: 2
}
Object.defineProperty(data, 'count', {
    enumerable: true,
    configurable: true,
    get: function () {
        console.log('你访问了count', this.count); // 循环读取data.count 导致报错
        return this.value
    },
    set: function (newVal) {
        console.log('你设置了count');
    }
})
console.log(data.count) // 报错 Maximum call stack size exceeded

写一个方法,当给定数字位数不足8位时,则在左边补充0以补足8位数的方法 

function padNumber(n, targetLen, placeholder) {
  const arr = ("" + n).split("");
  const diff = arr.length - targetLen;
  if (diff < 0) {
    return Array(0 - diff)
      .fill(placeholder, 0, 0 - diff + 1)
      .concat(arr)
      .join("");
  } else {
    return arr.join("");
  }
}
console.log(padNumber(3458, 8, "0")); //'00003458'
console.log(padNumber(90990, 3, "-")); //'90990'

innerHTML与outerHTML有什么区别? 

<div id="test"><h5>就是喜欢你</h5></div>

document.getElementById("test").innerHTML //<h5>就是喜欢你</h5> document.getElementById("test").outHTML //<div id="test"><h5>就是喜欢你</h5></div> 

js操作节点的方法有哪些? 

创建节点

    createElement() 创建一个元素节点
    createTextNode() 创建一个文本节点
    createAttribute() 创建一个属性节点
    createComment() 创建一个注释节点

插入节点

    appendChild() 把节点插入到父节点的末尾
    insertBefore() 把节点插入到父节点的某个兄弟节点的前面

删除节点

    removeChild()

查找节点

    getElementById()
    getElementsByTagName()
    getElementsByName()

替换节点

    replaceChild()

写一个格式化时间的方法

function dateToString(date, format = 'yyyy-MM-dd') {
  const d = new Date(date);
  let result = format;
  const _config = {
    'y+': d.getFullYear(),
    'M+': d.getMonth() + 1, // 月
    'd+': d.getDate(), // 日
    'h+': d.getHours(), // 小时
    'm+': d.getMinutes(), // 分
    's+': d.getSeconds(), // 秒
  };

  for (const reg in _config) {
    if (!(new RegExp(`(${reg})`).test(result))) continue;
    const match = RegExp.$1;
    let num = `${_config[reg]}`;
    while (num.length < match.length) { num = `0${num}` }
    result = result.replace(match, num);
  }

  return result;
}

function stringToDate(str, format = 'yyyy-MM-dd') {
  let args = [/y+/, /M+/, /d+/, /h+/, /m+/, /s+/];
  args = args.reduce((re, reg, index) => {
    const match = format.match(reg);
    const defaultValue = [1970, 0, 1, 0, 0, 0][index];
    if (!match) return re.concat([defaultValue]);
    var index = match.index;
    const num = Number(str.slice(index).match(/\d+/));
    return re.concat([num]);
  }, []);
  args.unshift(null);
  return new(Date.bind.apply(Date, args));
}

pjax和ajax的区别是什么? 

pjax 是一个 jQuery 插件,它通过 ajax 和 pushState 技术提供了极速的(无刷新 ajax 加载)浏览体验,并且保持了真实的地址、网页标题,浏览器的后退(前进)按钮也可以正常使用。
pjax 的工作原理是通过 ajax 从服务器端获取 HTML,在页面中用获取到的 HTML 替换指定容器元素中的内容。然后使用 pushState 技术更新浏览器地址栏中的当前地址。以下两点原因决定了 pjax 会有更快的浏览体验:
不存在页面资源(js/css)的重复加载和应用;
如果服务器端配置了 pjax,它可以只渲染页面局部内容,从而避免服务器渲染完整布局的额外开销。

微信小程序实现轨迹回放,微信原生小程序,基于uniapp的小程序?

需要写出轨迹拖动,进度条,开始,暂停,结束功能。

异步请求重试策略有哪些呢?

重试次数、重试时间间隔等

 写一个方法实现promise失败后自动重试

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>

</html>
<script>
    Promise.retry = (fun, limit = 5) => {
        return new Promise((resolve, reject) => {
            let __num = 1;
            let __run = () => {
                fun()
                    .then(res => {
                        resolve(res);
                    })
                    .catch(err => {
                        if (__num++ >= limit) {
                            reject(err)
                        } else {
                            console.log('retry again!!')
                            __run()
                        }
                    })
            }
            __run()
        })
    }

    let k = 0

    function test() {
        return new Promise((resolve, reject) => {
            if (++k >= 3) {
                resolve(k)
            } else {
                reject('hhh')
            }
        })
    }

    Promise.retry(test).then(res => {
            console.log('resolve: ', res)
        }).catch(err => {
            console.log('reject: ', err)
        })
        // retry again!!
        // retry again!!
        // resolve: 3
</script>

 运行结果
在这里插入图片描述

实现多张图片合成一张的效果 

原理是使用canvas画布。

在页面加载前 mounted方法里:
 

    setTimeout(() => {
     
        this.changeimg();
     
    }, 1000);

changeimg( )方法:

        changeimg(){
                var self = this;
                var imgsrcArray = [
                    require('../../assets/image/income/background.png'),
                    $('#qrcode canvas')[0].toDataURL('image/png')
                ];
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');
                canvas.width = 750;
                canvas.height = 1334;
                var imglen = imgsrcArray.length;
                var showContent = '我是'+this.showPhone;
                var drawimg = (function f(n){
                    if(n < imglen){
                        var img = new Image();
                        img.crossOrigin = 'Anonymous'; //解决跨域问题
                        img.onload = function(){
                            if(n == 0){
                                ctx.drawImage(img,0,0,750,1334);
                                ctx.font = '30px Adobe Ming Std';
                                
                                ctx.fillText(showContent,250,800);
                                ctx.fillText('文字文字',250,850);
                            }else{
                                ctx.drawImage(img,250,900,250,250);
                            }
                            f(n+1);
                        }
                        
                        img.src = imgsrcArray[n];
                    }else{
                        self.downloadUrl = canvas.toDataURL("image/jpeg");
                        self.downloadUrl = self.downloadUrl.replace("data:image/jpeg;base64,", "");
                    }
                })(0);
            }

本例中将一张背景图与二维码图合成一张图片 ,

二维码的生成也需要在此方法之前。

html:

    <img style="width:100%;" :src="'data:image/jpeg;base64,' + downloadUrl" alt="">
    <div style="opacity:0;position:absolute;bottom:0;" id="qrcode"></div>

ajax如何接收后台传来的图片?

1.设置responseType为 Blob,2.将Blob保存为文件

 js源代码压缩都有哪些方法?它们的压缩原理分别是什么

方法
1.在线工具
2.webpack

原理
1.删除注释
2.变量名方法名字符精减

不用 + eval Function 实现加法
 

// 使用位运算符实现
function add (a, b) {
    if (a == 0 || b == 0) {
        return a || b;
    }
    while (b != 0) {
        let i = b;
        b = (a & b) << 1;
        a = a ^ i;
    }
    return a;
};

 写一个 document.querySelector 的逆方法

document.queryNode = function(node){
    if(node.id){
        return '#'+ node.id;
    }
    if(node.className){
        return '.'+ node.id;
    }
    return node.nodeName; 
};

如何判断对象是否属于某个类?


    obj.proto === class.prototype 可以递归去找
    obj instanceof class

说说你对js沙箱的理解,它有什么应用场景?

在微前端有用到js沙箱,例如qiankun框架,主应用的js运行和子任务的js运行不会相互影响,是使用es6的proxy来实现的

纯函数和函数式编程有什么关系?

函数式编程是一种编程思想,纯函数是这种思想的基本

要实现函数式编程,我们所封装的方法应该是抽象的,应该是和外部状态无关系的,也就需要是纯函数的,这样才能保证抽象的方法可复用而且输出结果只决定于输入参数。

为什么要用纯函数? 

在此之前要先了解什么是纯函数,简单来说纯函数的定义有两个:
1.返回的结果只依赖于传入的参数。
2.执行过程中不产生副作用。
在这里就需要了解到什么是副作用
1.改变了外部变量或者对象属性
2.触发任何外部进程
3.发送http请求
4.调用其他有副作用的函数
5.……
那么我们为什么要用纯函数呢,从定义来看,我们可以知道纯函数不管你在什么时候请求它,只要参数是一样的,那返回的结果就肯定是一样的。
然后对于副作用我的理解是一个函数的功能要单一 ,你不能即在负责计算或者什么别的行为的同时还负责http请求什么的,发起http请求应该让另外一个函数去单独实现。
然后另外一个函数虽然产生了副作用,但是它的返回结果只依赖于传入的参数(比如链接)。这样做的好处有方便测试和后期维护,如何你一个函数负责多个功能,那后面估计看着这个函数都很难受。

使用js实现一个图片剪裁的功能

/** 
 * 裁切图片
 * @param imgUrl 原始图片路径
 * @param x,y,width,height 从点[x, y]开始,将宽度width,高度height的区域裁切下来
 * tips:需要运行于服务器环境下切图片为同域
 */
function clipImage(imgUrl, x, y, width, height) {
    return new Promise((resolve, reject) => {
        let cvs = document.createElement("canvas");
        cvs.width = width;
        cvs.height = height;

        var ctx = cvs.getContext('2d');
        let _img = new Image();
        _img.src = imgUrl;
        _img.onload = () => {
            ctx.drawImage(_img, 0 - x, 0 - y);
            resolve(cvs.toDataURL());
        }
    })
}

clipImage("./img/loginbg.jpg", 100, 100, 300, 400).then(res => {
    let __img = document.createElement("img");
    __img.src = res;
    document.body.appendChild(__img);
})

使用for-in语句能保证遍历对象的顺序吗?如果不能那为什么?如果可以那又如何保证? 

Chrome Opera 的 JavaScript 解析引擎遵循的是新版 ECMA-262 第五版规范。因此,使用 for-in 语句遍历对象属性时遍历顺序并非属性构建顺序。而 IE6 IE7 IE8 Firefox Safari 的 JavaScript 解析引擎遵循的是较老的 ECMA-262 第三版规范,属性遍历顺序由属性构建的顺序决定。

如果我们需要按照一定的顺序输出属性,也可以先在一个数组中保存所有的key值,再将数组排序,最后循环这个key数组,通过key获取对象中的属性即可:
var user = {};
if(!user['Jason']) {
user['Jason'] = [];
}
user['Jason']['grade'] = 3;
user['Jason']['level'] = 1;

if(!user['arthinking']) {
user['arthinking'] = [];
}
user['arthinking']['grade'] = 4;
user['arthinking']['level'] = 1;

console.log(user);

for(var key in user){
console.log('key: ', key);
}

var keys = [];
for(var key in user){
keys.push(key);
}
keys = keys.sort();

for(var i=0; i<keys.length; i++){
var key = keys[i];
console.log(user[key]['grade']);
}

写个方法获取屏幕的DPI 

 (function() {
	var arrDPI = new Array();
	if ( window.screen.deviceXDPI) {
		arrDPI[0] = window.screen.deviceXDPI;
		arrDPI[1] = window.screen.deviceYDPI;
	}
	else {
		var tmpNode = document.createElement( "DIV" );
		tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
		document.body.appendChild( tmpNode );
		arrDPI[0] = parseInt( tmpNode.offsetWidth );
		arrDPI[1] = parseInt( tmpNode.offsetHeight );
		tmpNode.parentNode.removeChild( tmpNode );
	}
	console.dir(arrDPI);
	return arrDPI;
})()

promise的构造函数是同步执行还是异步执行,它的then方法呢?

promise构造函数是同步执行的,then方法是异步执行的。

内存泄漏和内存溢出有什么区别

内存泄露:用动态储存分配函数内存空间,在使用完毕后未释放,导致一直占据该内存单元,直到程序结束。

内存溢出:不顾堆栈分配的局部数据块大小,向数据块中写入过多数据,导致数据越界,结果覆盖了别的数据。常在递归中发生。

 写一个方法把科学计数法转换成数字或者字符串

function c(a) {
return a.replace(/^(\d+)(?:.(\d+))*eE(\d+)/,(_,a,a1,p,n)=>{
a1=a1||''
if(p==='-'&&n>0) {
return '0.'+'0'.repeat(n-1)+a+a1
}else{
return a+(a1.length>n? a1.substr(0, n)+'.'+a1.substr(n): a1+'0'.repeat(n-a1.length))
}
})
}

写一个方法,实时验证input输入的值是否满足金额如:3.56(最多只有两位小数且只能数字和小数点)的格式,其它特殊字符禁止输入

<body>
  <input type="text" id="amount">
  <em id="message"></em>
</body>
<script>
  const amountInput = document.getElementById('amount');
  const msg = document.querySelector('#message');
  amountInput.oninput = function (event) {
    let value = event.target.value;
    let pat = /^\d+(\.\d{1,2})?$/;
    msg.innerHTML = pat.test(value) ? 'True' : 'False';
  }
</script>

更优方式---input的 pattern属性

<input type="text" pattern="^\d+(\.\d{1,2})?$">

使用delete删除数组,其长度会改变吗 

使用delete删除数组元素,其长度会改变吗?



咱来写个案例🌰看看就知道了:

var arr = [1, 2, 3]
delete arr[1]
console.log(arr)
console.log(arr.length)

结果如下:

通过结果,我们可以得出结论:使用delete删除数组元素,其长度是不会改变的。

关于这一点,大家可以把数组理解为是一个特殊的对象,其中的每一项转换为对象的伪代码为:

key: value
// 对应:
0: 1
1: 2
2: 3
length: 3

所以我们使用delete操作符删除一个数组元素时,相当于移除了数组中的一个属性,被删除的元素已经不再属于该数组。但是这种改变并不会影响数组的length属性。

扩展:

    如果你想让一个数组元素继续存在但是其值是 undefined,那么可以使用将 undefined 赋值给这个元素而不是使用 delete。例如:arr[1] = undefined
    如果你想通过改变数组的内容来移除一个数组元素,请使用splice() 方法。例如:arr.splice(1, 1)

 代码中如果遇到未定义的变量,会抛出异常吗?程序还会不会继续往下走?

在浏览器环境下

JS 解析器解析到未定义变量时,会抛出 Uncaught ReferenceError 错误,JS 引擎会停止解析后面的代码,但之前的代码不受影响,并跳出该代码块。

下面看看具体代码。

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>
代码中如果遇到未定义的变量,会抛出异常吗?程序还会不会继续往下走
    </title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <script>
      console.log(1);
    </script>
    <script>
      console.log(2);
      console.log(a);
      console.log(3);
    </script>
    <script>
      console.log(4);
    </script>
  </body>
</html>

    JS 引擎首先加载第一个<script></script> 代码块,进入解析的三个流程(1、词法语法分析,2、预解析,3、执行阶段),之后在控制台输出 1;
    接着 JS 引擎加载第二个<script></script> 代码块,进入解析的三个流程,控制台输出 2,当解析 console.log(a); 这行代码时,因为 a 是未定义的变量,会抛出 Uncaught ReferenceError 错误,JS 引擎会停止解析后面的代码,即 JS 引擎不会执行 console.log(3); 这行代码,并跳出第二个<script></script> 代码块;
    立马加载第三个<script></script> 代码块,进入解析的三个流程,在控制台输出 4。

说说你对JSBridge的理解

js和原生应用之间交互的桥梁

 js循环中调用异步的方法,如何确保执行结果的顺序是正确的?

An example:

let searchApi = function(arg){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log(arg)
            resolve(arg)
        }, 20)
    })
};
let fields = [1,2,3,4];
let arr = new Array(fields.length);
await fields.forEach(async (vvv, index) => {
    const obj = await searchApi(vvv)
    arr[index] = obj
    const tempArr = JSON.parse(JSON.stringify(arr))
    if(tempArr.every(Boolean)){
        console.log(arr)
    }
})

写一个方法遍历指定对象的所有属性

Object.keys()、Object.values()只能遍历对象自有的属性,for in 可以遍历原型中的属性。

实现一个函数记忆的方法

同步运算结果缓存,这个就老生常谈了:

function useCache(func) {
  var cache = {};
  return function() {
    var key = arguments.length + Array.prototype.join.call(arguments);
    if(cache[key]) return cache[key];
    cache[key] = func.apply(this, arguments);
    return cache[key];
  }
}

手写一个trim()的方法 

function trim(str) {
if (str[0] === ' ' && str[str.length - 1] === ' ') {
return trim(str.substring(1, str.length - 1))
} else if (str[0] !== ' ' && str[str.length - 1] === ' ') {
return trim(str.substring(0, str.length - 1))
} else if (str[0] === ' ' && str[str.length - 1] !== ' ') {
return trim(str.substring(1, str.length))
} else if (str[0] !== ' ' && str[str.length - 1] !== ' ') {
return str;
}
}

你是如何比较js函数的执行速度的? 

采用chrome performance api


console.time(flag);
console.timeEnd(flag);


performance api比较精准的
console.time(flag);
也行 搭配timelog 多次测量

axios拦截器原理是什么?

拦截器原理其实就是用use添加用户自定义的函数到拦截器的数组中。
最后把他们放在拦截器请求前,请求后。组成promise链式调用。

  // 组成`Promise`链
  // Hook up interceptors middleware
  // 把 xhr 请求 的 dispatchRequest 和 undefined 放在一个数组里
  var chain = [dispatchRequest, undefined];
  // 创建 Promise 实例
  var promise = Promise.resolve(config);

 // 遍历用户设置的请求拦截器 放到数组的 chain 前面
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

 // 遍历用户设置的响应拦截器 放到数组的 chain 后面
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

 // 遍历 chain 数组,直到遍历 chain.length 为 0
  while (chain.length) {
    // 两两对应移出来 放到 then 的两个参数里。
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;

fetch和axios请求的原理都是基于XMLHttpRerequst吗?

axios是基于XMLHttpRequest的封装,而fetch是js原生支持的网络请求api,这在浏览器底层进行了支持。

举例说明面向对象编程有什么缺点?

有实例化开销,内存消耗比较大,性能消耗比较大

使用js实现一个循环队列 

const queue = []
let queueRunning = false
let loopTimer = null
const loop = task => {
  // do something...
  console.log(task)
  if (isQueueHasTask()) {
    // you can add new tasks in the middle of the queue.
    loopTimer = setTimeout(() => {loop(getNextTask())})
  } else {
    queueRunning = false
  }
}
const startLoop = () => {
  if (queueRunning) return
  if (isQueueHasTask()) {
    queueRunning = true
    loop(getNextTask())
  }
}
const stopLoop = () => {
  if (!queueRunning) return
  if (loopTimer) {
    clearTimeout(loopTimer)
    queueRunning = false
  }
}
const getNextTask = () => queue.shift()
const isQueueHasTask = () => !!queue.length
const addTask = task => {
  if (!Array.isArray(task)) task = [task]
  queue.push(...task)
  startLoop()
}

Number()的存储空间是多大?假如接口返回一个超过最大字节的数字怎么办? 

Number类型的最大值为2的53次方,即9007199254740992,如果超过这个值,比如900719925474099222,那么得到的值会不精确,也就是900719925474099200

alert如何让文本换行?

先考虑兼容性的问题,再使用转义字符
ie: alert("A\r\nB"); //chrome也可以实现
chrome: alert("A\nB");

一个api接口从请求数据到请求结束共与服务器进行了几次交互?

 API是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。
    如果已经建立了连接,那么单次请求数据到请求结束应该是一次交互;
    如果没有建立连接,根据协议不同可能会不同吧, 像ajax轮询和websocket与服务器建立连接后的数据流向就存在区别

js的循环结构有哪些?

for
for in
while
do while

innerHTML有什么缺点?

innerHTML的修改和添加,元素中旧的内容被移除新的内容会被重新写入元素。innerHTML内容将会被重新解析和构建元素。例如
innerHTML+ = ”“ 时,内容”归零” 重写,所有的图片和资源重新加载,这样会消耗更多的时间。

举例说明js中什么是尾调用优化

写在前面
上次介绍了什么是尾调用以及怎么准确快速的判别一个函数调用是否为尾调用。那么,我们判别尾调用的意义是什么呢?做什么事情总归有个目的,那么今天我们就来系统的介绍一下尾调用的意义,或者说尾调用有什么用吧。

2. 尾调用优化

我们知道,函数的调用会在内存中生成一个“调用帧”(call frame),保存着函数的调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方还会生成一个B的调用帧。等到函数B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B的内部还调用函数C,那么在B的调用帧上方又会生成一个C的调用帧,以此类推。所有的调用帧就形成了一个“调用栈”(call stack)。

我们知道,尾调用是函数的最后一步操作,外部函数的调用位置和其内部变量信息都不会再用到了,所以不需要保留外部函数的调用帧了,直接用内层函数的调用帧取代外层函数的调用帧即可。

如果所有的函数都是尾调用,那么完全可以做到每次执行时的调用帧只有一项,这将大大的节省内存,更不可能发生内存溢出,这就是讨论尾调用的意义所在。

所谓的“尾调用优化”,其实就是保证在函数执行时只保留内层函数的调用帧,换句话说,就是用内层函数的调用帧取代外部函数的调用帧,即执行时内存中只保存一项调用帧。
3. 如何做到尾调用优化

通过上面的讨论,我们知道了,只要外部函数的调用帧被内层函数的调用帧取代即可做到“尾调用优化”。同时,我们也知道调用帧是用来保存函数调用位置和内部变量信息的,所以,要想让内层函数的调用帧取代外部函数的调用帧,只需要保证,在调用内层函数时,不再用到和外部函数有关的一切信息即可(不再用到外部函数的内部变量)。

如此,我们总结出只有内层函数在被调用时不再用到外部函数的内部变量,才能做到“尾调用优化”。

先来,看一个例子:

function f(a) {
    var b = 3;
    function g(n) {
      return n + b
    }
    return g(a)
}


上述例子,我们明显可以看出这是典型的尾调用,那么这有没有做到“尾调用优化”呢?很明显,可以看出在调用内部函数g的时候,其内部用到外部函数f 的内部变量b,故在函数g 被调用时,g 的调用帧并不能取代外部函数f 的调用帧,所以这不是“尾调用优化”。

如何判断两个对象相等?

提供另一种写法:

function isSameObject(object1, object2) {
    if (Object.prototype.toString.call(object1) === '[object Object]' &&
        Object.prototype.toString.call(object2) === '[object Object]') {
        if (Object.keys(object1).length !== Object.keys(object2).length) {
            return false;
        }
        for (let key in object1) {
            if (!object2[key] || object1[key] !== object2[key]) {
                return false;
            }
        }
    }
    return true;
}

当然JSON.stringify(obj)==JSON.stringify(obj)执行速度是最快的
 

 字符串拼接有哪些方式?哪种性能好?

1.使用 + 号
2.es6模板字符串,以反引号( ` )标识
3.concat
4.数组方法join

性能最好的是连接: +
继续补充:

    Array.prototype.reduce
    String.prototype.padStart
    String.prototype.padEnd

localStorage什么时候过期?

默认不会过期,除非清楚浏览器缓存或者手动删除,可以通过setItem里面缓存时间参数,取出来后做一个前后时间对比,如果超过时间限制的话就删除该缓存即可。

写一个获取非行间样式的方法

window.getComputedStyle()?window.getComputedStyle(element).attribute:element.currentStyle.attribute

写一个获取页面中所有checkbox的方法

function getAllCheckbox() {
  return [...document.querySelectorAll('input[type=checkbox]')]
}

 XML与JSON有什么的区别?

xml 可以设 id,用 include 之类的可以直接引用过来,甚至可以约定内容格式。
但 json 不依赖 js 等语言就很难完成了。

好吧,广义上来讲,
json 编译更简单易懂,体积更小,结构不复杂更易于传输。
xml 有约定的标准或格式,属性结构的感觉更强。

这种结构上的不同感觉可以看看下面体会一下

<country>
  <name>中国</name>
  <province>
    <name>湖北</name>
    <citys>
     <city>武汉</city>
    </citys>
  </province>
  <nation>
    <name>汉族</name>
  </nation>
  <nation>
    <name>壮族</name>
  </nation>
</country>

{
  "name": "中国",
  "provinces": [
    {
      "name": "湖北",
      "citys": [
        {
          "city": "武汉" 
        }
      ]
    }
  ],
  "nation": [
    {
      "name": "汉族"
    },
    {
      "name": "壮族"
    }
  ]
}



xml是可标记扩展语言,JSON(javascript object notation)是基于javascript的一个子集,xml比json具有更清晰的层次结构,但是需要更多的字符进行描述,json比xml更流行。

flash如何与js交互?

当Flash置于HTML容器中时,经常会遇到AS与JS的通信问题,例如:JS能否调用AS中的变量、方法,AS能否调用JS中的变量、方法等等。答案是肯定的。随着技术的不断发展,解决方案也是多种多样的。
在我总结的HTML与FLASH之间的“静态”传值一文中提到了JS使用SetVariable方法来设置FLASH中的变量,kinglong认为此法已经过时。对此我表示同意,但上文重点毕竟不是在讨论JS与AS的通信,因此另外对AS与JS通信做一个个人总结,欢迎大家讨论。
实现JS跟AS的通信,目前可选方法比较多,但早期的方法在使用便捷和功能上都不是很完美,下面做一简要说明:
  一、getURL  getURL("javascript:history.go(-1)");

通过URL协议来访问页面中的javascript,上面控制浏览器历史的代码很眼熟吧,诸如此类我们平时在定制页面收藏,发送邮件时都会经常使用这种方式。虽然你也可以调用页面中自己定义的JS函数,但我个人认为局限性还是比较大,函数的参数传递并不是很灵活,无返回值,而且只能实现AS调用JS,反之不行。

  二、fscommand命令
使用fscommand来调用AS定义的方法也是一个很常用的方式,但我们需要在HTML页面中定义一个具有规定格式的myFlash_DoFSCommand函数,首先定义这个函数我个人就觉得麻烦,而且也只能实现AS调用JS,无函数返回值。

  三、SetVariable  
上面两种方法都只能实现AS调用JS,而SetVariable恰恰相反,只要我们稍微做下处理,他就可以帮我们变相调用AS中的方法。大概思路如下:AS中设置一个状态变量,并使用Object的watch方法对其监视,JS通过SetVariable来修改这个状态变量,一旦侦测到了变量的改变,那就可以根据不同的状态值来选择执行AS中的相应函数了。如果需要考虑用户的低版本播放器,那么你可以考虑下该方法,个人认为还是比较灵活的。

  可以看出上面的这些做法都有一定的局限性,所以在我们的应用中很多时候都不得不将他们结合使用。而下面我要具体介绍的就是 ExternalInterface的做法,通过它你能轻松实现AS与JS的双向方法调用,从而也解决了双向的变量访问,详细介绍可参见FLASH帮助文档和Adobe的官方教程,下面用两个简单例子来说明ExternalInterface的使用。

一、AS调用JS的方法(实例演示)

Flash中代码:
//导入包
import flash.external.*;
get_btn.onRelease = function(){
//调用页面中的JS函数
var temp_str = String(ExternalInterface.call("say", "Hello,World"));
result_txt.text = temp_str;
}
Html中代码:function say(txt){
return txt;
}

没错,就这么简单,JS函数定义没有任何要求,AS中使用call方法直接调用就可以了。

二、JS调用AS的方法(实例演示)

FLASH中代码://导入包
import flash.external.*;
//提供JS访问的函数名
var _method:String = "say";
//指定本地函数中this变量的作用域,可设置为null留空
var e_area:Object = null;
//AS内部函数名
var method:Function = say;
//将函数注册到容器列表
var wasSuccessful:Boolean = ExternalInterface.addCallback(_method, e_area, method);
//查看注册是否成功
if(wasSuccessful){
result_txt.text = "函数注册成功";
}
//本地的函数
function say(txt:String) {
result_txt.text = txt;
}
Html中代码:<div>
<form>
<input type="button" οnclick="callExternalInterface()" value="JS调用AS方法" />
</form>
<script>
function callExternalInterface() {
thisMovie("demo").say("Hello,World");
}
//浏览器兼容访问DOM
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName]
}
else {
return document[movieName]
}
}
</script>
</div>
其中红色代码是核心代码,其作用原理是AS端通过addCallback函数把AS内部定义的方法注册为可从容器中调用,允许自定义另外一个方法名供 JS来调用这个方法,函数调用成功返回true,失败返回flase,在此例中通过wasSuccessful变量来判断函数是否注册成功。函数注册成功以后,JS可以通过DOM来访问SWF对象,然后直接调用预定义的方法即可。

通过比较可以看出,使用ExternalInterface来完成AS和JS的通信,代码可以更简洁,更清晰,功能也更强大,不过还有些细节你需要了解,需要使用8.0以上的播放器,对于调用的JS函数不能使用递归,同时安全域限制也必须在考虑之中。

说说你对js对象生命周期的理解

start



一切皆对象

咱们经常听到JS中“一切皆对象”?有没有问想过这是什么意思?其它语言也有“一切皆对象”之说,如Python。但是Python中的对象不仅仅是像JS对象这样的存放值和值的容器。Python中的对象是一个类。JS中有类似的东西,但JS中的“对象”只是键和值的容器:

var obj = { name: “Tom”, age: 34 }

实际上,JS中的对象是一种“哑”类型,但很多其他实体似乎都是从对象派生出来的。甚至是数组,在JS中创建一个数组,如下所示:

var arr = [1,2,3,4,5]

然后用typeof运算符检查类型,会看到一个令人惊讶的结果:

typeof arr
"object"

看来数组是一种特殊的对象!即使JS中的函数也是对象。如果你深入挖掘,还有更多,创建一个函数,该函数就会附加一些方法:

var a = function(){ return false; }
a.toString()
1
2
输出:

“function(){ return false; }”

咱们并没有在函数声明toString方法,所以在底层一定还有东西。它从何而来?Object有一个名为.toString的方法。似乎咱们的函数具有相同的Object方法。

Object.toString()

这时咱们使用浏览器控制台来查看默认被附加的函数和属性,这个谜团就会变得更加复杂:

640?wx_fmt=png

谁把这些方法放在函数呢。 JS中的函数是一种特殊的对象,这会不会是个暗示? 再看看上面的图片:我们的函数中有一个名为prototype的奇怪命名属性,这又是什么鬼?

JS中的prototype是一个对象。它就像一个背包,附着在大多数JS内置对象上。例如 Object, Function, Array, Date, Error,都有一个“prototype”:

typeof Object.prototype // 'object'
typeof Date.prototype // 'object'
typeof String.prototype // 'object'
typeof Number.prototype // 'object'
typeof Array.prototype // 'object'
typeof Error.prototype // 'object'

注意内置对象有大写字母:

String

Number

Boolean

Object

Symbol

Null

Undefined

以下除了Object是类型之外,其它是JS的基本类型。另一方面,内置对象就像JS类型的镜像,也用作函数。例如,可以使用String作为函数将数字转换为字符串:

String(34)

现在回到“prototype”。prototype是所有公共方法和属性的宿主,从祖先派生的“子”对象可以从使用祖先的方法和属性。也就是说,给定一个原始 prototype,咱们可以创建新的对象,这些对象将使用一个原型作为公共函数的真实源,不 Look see see。

假设有个要求创建一个聊天应用程序,有个人物对象。这个人物可以发送消息,登录时,会收到一个问候。

根据需求咱们很容易定义这个么一 Person 对象:

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};

你可能会想知道,为什么这里要使用字面量的方式来声明 Person 对象。稍后会详细说明,现在该 Person 为“模型”。通过这个模型,咱们使用 Object.create() 来创建以为这个模型为基础的对象。
创建和链接对象

JS中对象似乎以某种方式链接在一起,Object.create()说明了这一点,此方法从原始对象开始创建新对象,再来创建一个新Person 对象:

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);

现在,Tom 是一个新的对象,但是咱们没有指定任何新的方法或属性,但它仍然可以访问Person中的name和age 属性。

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);
 
var tomAge = Tom.age;
var tomName = Tom.name;
 
console.log(`${tomAge} ${tomName}`);
 
// Output: 0 noname

现在,可以从一个共同的祖先开始创建新的person。但奇怪的是,新对象仍然与原始对象保持连接,这不是一个大问题,因为“子”对象可以自定义属性和方法

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);
 
Tom.age = 34;
Tom.name = "Tom";
var tomAge = Tom.age;
var tomName = Tom.name;
 
console.log(`${tomAge} ${tomName}`);
 
// Output: 34 Tom

这种方式被称为“屏蔽”原始属性。还有另一种将属性传递给新对象的方法。Object.create将另一个对象作为第二个参数,可以在其中为新对象指定键和值:

var Tom = Object.create(Person, {
  age: {
    value: 34
  },
  name: {
    value: "Tom"
  }
});

以这种方式配置的属性默认情况下不可写,不可枚举,不可配置。不可写意味着之后无法更改该属性,更改会被忽略:

var Tom = Object.create(Person, {
  age: {
    value: 34
  },
  name: {
    value: "Tom"
  }
});
 
Tom.age = 80;
Tom.name = "evilchange";
 
var tomAge = Tom.age;
var tomName = Tom.name;
 
Tom.greet();
 
console.log(`${tomAge} ${tomName}`);
 
// Hello Tom
// 34 Tom

不可枚举意味着属性不会在 for…in 循环中显示,例如:

for (const key in Tom) {
  console.log(key);
}
 
// Output: greet

但是正如咱们所看到的,由于JS引擎沿着原型链向上查找,在“父”对象上找到greet属性。最后,不可配置意味着属性既不能修改也不能删除。

Tom.age = 80;
Tom.name = "evilchange";
delete Tom.name;
var tomAge = Tom.age;
var tomName = Tom.name;
 
console.log(`${tomAge} ${tomName}`);
 
// 34 Tom

如果要更改属性的行为,只需配writable(可写性),configurable(可配置),enumerable(可枚举)属性即可。

var Tom = Object.create(Person, {
  age: {
    value: 34,
    enumerable: true,
    writable: true,
    configurable: true
  },
  name: {
    value: "Tom",
    enumerable: true,
    writable: true,
    configurable: true
  }
});

现在,Tom也可以通过以下方式访问greet():

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);
 
Tom.age = 34;
Tom.name = "Tom";
var tomAge = Tom.age;
var tomName = Tom.name;
Tom.greet();
 
console.log(`${tomAge} ${tomName}`);
 
// Hello Tom
// 34 Tom

暂时不要过于担心“this”。拉下来会详细介绍。暂且先记住,“this”是对函数执行的某个对象的引用。在咱们的例子中,greet() 在Tom的上下文中运行,因此可以访问“this.name”。
构建JavaScript对象

目前为止,只介绍了关于“prototype”的一点知识 ,还有玩了一会 Object.create()之外但咱们没有直接使用它。随着时间的推移出现了一个新的模式:构造函数。使用函数创建新对象听起来很合理, 假设你想将Person对象转换为函数,你可以用以下方式:

function Person(name, age) {
  var newPerson = {};
  newPerson.age = age;
  newPerson.name = name;
  newPerson.greet = function() {
    console.log("Hello " + newPerson.name);
  };
  return newPerson;
}

因此,不需要到处调用object.create(),只需将Person作为函数调用:

var me = Person(“Valentino”);

构造函数模式有助于封装一系列JS对象的创建和配置。在这里, 咱们使用字面量的方式创建对象。这是一种从面向对象语言借用的约定,其中类名开头要大写。

上面的例子有一个严重的问题:每次咱们创建一个新对象时,一遍又一遍地重复创建greet()函数。可以使用Object.create(),它会在对象之间创建链接,创建次数只有一次。首先,咱们将greet()方法移到外面的一个对象上。然后,可以使用Object.create()将新对象链接到该公共对象:

var personMethods = {
  greet: function() {
    console.log("Hello " + this.name);
  }
};
 
function Person(name, age) {
  // greet lives outside now
  var newPerson = Object.create(personMethods);
  newPerson.age = age;
  newPerson.name = name;
  return newPerson;
}
 
var me = Person("Valentino");
me.greet();
 
// Output: "Hello Valentino"

这种方式比刚开始会点,还可以进一步优化就是使用prototype,prototype是一个对象,可以在上面扩展属性,方法等等。

Person.prototype.greet = function() {
  console.log("Hello " + this.name);
};

移除了personMethods。调整Object.create的参数,否则新对象不会自动链接到共同的祖先:

function Person(name, age) {
  // greet lives outside now
  var newPerson = Object.create(Person.prototype);
  newPerson.age = age;
  newPerson.name = name;
  return newPerson;
}
 
Person.prototype.greet = function() {
  console.log("Hello " + this.name);
};
 
var me = Person("Valentino");
me.greet();
 
// Output: "Hello Valentino"

现在公共方法的来源是Person.prototype。使用JS中的new运算符,可以消除Person中的所有噪声,并且只需要为this分配参数。

下面代码:

function Person(name, age) {
  // greet lives outside now
  var newPerson = Object.create(Person.prototype);
  newPerson.age = age;
  newPerson.name = name;
  return newPerson;
}
 

改成: 

function Person(name, age) {
  this.name = name;
  this.age = age;
}



完整代码:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
 
Person.prototype.greet = function() {
  console.log("Hello " + this.name);
};
 
var me = new Person("Valentino");
me.greet();
 
// Output: "Hello Valentino"


注意,使用new关键字,被称为“构造函数调用”,new 干了三件事情

创建一个空对象

将空对象的proto指向构造函数的prototype

使用空对象作为上下文的调用构造函数

function Person(name, age) {

根据上面描述的,new Person(“Valentino”) 做了:

创建一个空对象:var obj = {}

将空对象的proto__`指向构造函数的 prototype:`obj.__proto = Person().prototype

使用空对象作为上下文调用构造函数:Person.call(obj)

检查原型链

检查JS对象之间的原型链接有很多种方法。例如,Object.getPrototypeOf是一个返回任何给定对象原型的方法。考虑以下代码:

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);

检查Person是否是Tom的原型:

var tomPrototype = Object.getPrototypeOf(Tom);
 
console.log(tomPrototype === Person);
 
// Output: true



当然,如果使用构造函数调用构造对象,Object.getPrototypeOf也可以工作。但是应该检查原型对象,而不是构造函数本身:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
 
Person.prototype.greet = function() {
  console.log("Hello " + this.name);
};
 
var me = new Person("Valentino");
 
var mePrototype = Object.getPrototypeOf(me);
 
console.log(mePrototype === Person.prototype);
 
// Output: true



除了Object.getPrototypeOf之外,还有另一个方法isPrototypeOf。该方法用于测试一个对象是否存在于另一个对象的原型链上,如下所示,检查 me 是否在 Person.prototype 上:

Person.prototype.isPrototypeOf(me) && console.log(‘Yes I am!’)

instanceof运算符也可以用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。老实说,这个名字有点误导,因为JS中没有“实例”。在真正的面向对象语言中,实例是从类创建的新对象。请考虑Python中的示例。咱们有一个名为Person的类,咱们从该类创建一个名为“tom”的新实例:

class Person():
    def __init__(self, age, name):
        self.age = age;
        self.name = name;
 
    def __str__(self):
        return f'{self.name}'
 
 
tom = Person(34, 'Tom')



注意,在Python中没有new关键字。现在,咱们可以使用isinstance方法检查tom是否是Person的实例

isinstance(tom, Person)
 
// Output: True

Tom也是Python中“object”的一个实例,下面的代码也返回true:

isinstance(tom, object)
 
// Output: True



根据isinstance文档,“如果对象参数是类参数的实例,或者是它的(直接、间接或虚拟)子类的实例,则返回true”。咱们在这里讨论的是类。现在让咱们看看instanceof做了什么。咱们将从JS中的Person函数开始创建tom(因为没有真正的类)

function Person(name, age) {
  this.name = name;
  this.age = age;
}
 
Person.prototype.greet = function() {
  console.log(`Hello ${this.name}`);
};
 
var tom = new Person(34, "Tom");



使用isinstance方法检查tom是否是Person和 Object 的实例

if (tom instanceof Object) {
  console.log("Yes I am!");
}
 
if (tom instanceof Person) {
  console.log("Yes I am!");



因此,可以得出结论:JS对象的原型总是连接到直接的“父对象”和Object.prototype。没有像Python或Java这样的类。JS是由对象组成,那么什么是原型链呢?如果你注意的话,咱们提到过几次“原型链”。JS对象可以访问代码中其他地方定义的方法,这看起来很神奇。再次考虑下面的例子:

var Person = {
  name: "noname",
  age: 0,
  greet: function() {
    console.log(`Hello ${this.name}`);
  }
};
 
var Tom = Object.create(Person);
 
Tom.greet();



即使该方法不直接存在于“Tom”对象上,Tom也可以访问greet()。

这是JS的一个内在特征,它从另一种称为Self的语言中借用了原型系统。当访问greet()时,JS引擎会检查该方法是否可直接在Tom上使用。如果不是,搜索将继续向上链接,直到找到该方法。

“链”是Tom连接的原型对象的层次结构。在我们的例子中,Tom是Person类型的对象,因此Tom的原型连接到Person.prototype。而Person.prototype是Object类型的对象,因此共享相同的Object.prototype原型。如果在Person.prototype上没有greet(),则搜索将继续向上链接,直到到达Object.prototype。这就是咱们所说的“原型链”。
保护对象不受操纵

大多数情况下,JS 对象“可扩展”是必要的,这样咱们可以向对象添加新属性。但有些情况下,我们希望对象不受进一步操纵。考虑一个简单的对象:

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};



默认情况下,每个人都可以向该对象添加新属性

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
superImportantObject.anotherProperty = "Hei!";
 
console.log(superImportantObject.anotherProperty); // Hei!

Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
Object.preventExtensions(superImportantObject);
 
superImportantObject.anotherProperty = "Hei!";
 
console.log(superImportantObject.anotherProperty); // undefined



这种技术对于“保护”代码中的关键对象非常方便。JS 中还有许多预先创建的对象,它们都是为扩展而关闭的,从而阻止开发人员在这些对象上添加新属性。这就是“重要”对象的情况,比如XMLHttpRequest的响应。浏览器供应商禁止在响应对象上添加新属性

var request = new XMLHttpRequest();
request.open("GET", "https://jsonplaceholder.typicode.com/posts");
request.send();
request.onload = function() {
  this.response.arbitraryProp = "我是新添加的属性";
  console.log(this.response.arbitraryProp); // undefined
};



这是通过在“response”对象上内部调用Object.preventExtensions来完成的。您还可以使用Object.isExtensible方法检查对象是否受到保护。如果对象是可扩展的,它将返回true:

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
Object.isExtensible(superImportantObject) && console.log("我是可扩展的");



如果对象不可扩展的,它将返回false:

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
Object.preventExtensions(superImportantObject);
 
Object.isExtensible(superImportantObject) ||
  console.log("我是不可扩展的!");



当然,对象的现有属性可以更改甚至删除

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
Object.preventExtensions(superImportantObject);
 
delete superImportantObject.property1;
 
superImportantObject.property2 = "yeees";
 
console.log(superImportantObject); // { property2: 'yeees' }



现在,为了防止这种操作,可以将每个属性定义为不可写和不可配置。为此,有一个方法叫Object.defineProperties。

var superImportantObject = {};
 
Object.defineProperties(superImportantObject, {
  property1: {
    configurable: false,
    writable: false,
    enumerable: true,
    value: "some string"
  },
  property2: {
    configurable: false,
    writable: false,
    enumerable: true,
    value: "some other string"
  }
});



或者,更方便的是,可以在原始对象上使用Object.freeze:

var superImportantObject = {
  property1: "some string",
  property2: "some other string"
};
 
Object.freeze(superImportantObject);



Object.freeze工作方式与Object.preventExtensions相同,并且它使所有对象的属性不可写且不可配置。唯一的缺点是“Object.freeze”仅适用于对象的第一级:嵌套对象不受操作的影响。
class

有大量关于ES6 类的文章,所以在这里只讨论几点。JS是一种真正的面向对象语言吗?看起来是这样的,如果咱们看看这段代码

class Person {
  constructor(name) {
    this.name = name;
  }
 
  greet() {
    console.log(`Hello ${this.name}`);
  }
}



语法与Python等其他编程语言中的类非常相似:

class Person:
    def __init__(self, name):
        self.name = name
 
    def greet(self):
        return 'Hello' + self.name



或 PHP

class Person {
    public $name; 
 
    public function __construct($name){
        $this->name = $name;
    }
 
    public function greet(){
        echo 'Hello ' . $this->name;
    }
}



ES6中引入了类。但是在这一点上,咱们应该清楚JS中没有“真正的”类。一切都只是一个对象,尽管有关键字class,“原型系统”仍然存在。新的JS版本是向后兼容的,这意味着在现有功能的基础上添加了新功能,这些新功能中的大多数都是遗留代码的语法糖。
总结

JS中的几乎所有东西都是一个对象。从字面上看。JS对象是键和值的容器,也可能包含函数。Object是JS中的基本构建块:因此可以从共同的祖先开始创建其他自定义对象。然后咱们可以通过语言的内在特征将对象链接在一起:原型系统。

从公共对象开始,可以创建共享原始“父”的相同属性和方法的其他对象。但是它的工作方式不是通过将方法和属性复制到每个孩子,就像OOP语言那样。在JS中,每个派生对象都保持与父对象的连接。使用Object.create或使用所谓的构造函数创建新的自定义对象。与new关键字配对,构造函数类似于模仿传统的OOP类。
思考

如何创建不可变的 JS 对象?

什么是构造函数调用?

什么是构造函数?

“prototype” 是什么?

可以描述一下 new 在底层下做了哪些事吗?

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
 

END


在DOM上同时绑定两个点击事件(一个用捕获,一个用冒泡),事件总共会执行几次,先执行哪个事件?

两次
先捕获,后冒泡

json和对象有什么区别?

JSON 是对象,但对象不一定是 JSON。

对象是由属性和属性值组成,也就是 KEY->VALUE 对。
对象中的 value 可以是任意的数据类型,包括函数。而 JSON 中的 value 不能为函数。

script所在的位置会影响首屏显示时间吗

会,如果script放在头部,js的执行会阻塞dom树的构建

 callee和caller的区别和作用是什么?

arguments.callee 指的是当前函数
Function.caller 指的是调用当前函数的函数
举个例子:

function a() {
  console.log(arguments.callee);
  console.log(a.caller); 
}
function b() {
  a();
}
b();
a();

控制台输出:
[Function: a]
[Function: b]
[Function: a]
null

作用:可以用在需要函数自调用,或者调用父函数的场景下

AudioContext有什么应用场景? 

AudioContext实例有createAnalyzer(),可以实现音频可视化,还有一个gainNode,可以实现输入(MediaStream or HTMLMediaElement)音量的range调整

写一个方法获取图片的方向

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title></title>
<script>
function getOrientation(file, callback) {
var reader = new FileReader();
reader.onload = function(e) {

var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xFFD8)
{
return callback(-2);
}
var length = view.byteLength, offset = 2;
while (offset < length)
{
if (view.getUint16(offset+2, false) <= 8) return callback(-1);
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1)
{
if (view.getUint32(offset += 2, false) != 0x45786966)
{
return callback(-1);
}

var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
{
if (view.getUint16(offset + (i * 12), little) == 0x0112)
{
return callback(view.getUint16(offset + (i * 12) + 8, little));
}
}
}
else if ((marker & 0xFF00) != 0xFF00)
{
break;
}
else
{
offset += view.getUint16(offset, false);
}
}
return callback(-1);
};
reader.readAsArrayBuffer(file);
}
window.onload = function(){
var input = document.getElementById('input');
input.onchange = function(e) {
getOrientation(input.files[0], function(orientation) {
alert('orientation: ' + orientation);
});
}
};
</script>
</head>
<body>
<input id='input' type='file' />
</body>
</html>

在设置keyup监听事件后按F5刷新和按浏览器中刷新键刷新有什么区别?

按浏览器中刷新键刷新不会触发keyup事件

 用js写一个方法检测浏览器是否支持css3的属性

var div = document.createElement('div');
console.log(div.style.transition);
//如果支持的话, 会输出 ""
//如果不支持的话, 会输出 undefined.


a == ('1'||'2'||'3') ? false : true写法进行改进,写出你优化后的方法

![1,2,3].includes(+a)
or
!['1', '2', '3'].includes(a + '')
or
!{1: true, 2: true, 3: true}[a]

 Geolocation.getCurrentPosition()用来做什么的?在什么浏览器不受兼容?

获取当前设备的位置
Android不支持

setTimeout的第三个参数有什么用?

第三个参数将作为定时器到期触发的函数的参数

解释下深度优先遍历和广度优先遍历的区别及如何实现

1、深度优先采用堆栈结构,先进后出,所占的空间较小,执行时间较长;
2、广度优先采用队列结构先进先出,所占空间较大,执行时间短,空间换时间;

const data = [
        {
            name: 'a',
            children: [
                { name: 'b', children: [{ name: 'e' }] },
                { name: 'c', children: [{ name: 'f' }] },
                { name: 'd', children: [{ name: 'g' }] },
            ],
        },
        {
            name: 'a2',
            children: [
                { name: 'b2', children: [{ name: 'e2' }] },
                { name: 'c2', children: [{ name: 'f2' }] },
                { name: 'd2', children: [{ name: 'g2' }] },
            ],
        }
    ]

    //深度优先
    function byDeepth(data) {
        let result = []
        const map = (item) => {
            result.push(item.name)
            item.children && item.children.forEach((item) => map(item))
        }
        data.forEach((item) => {
            map(item)
        })
        return result.join(',')
    }

    //广度优先
    function byBreadth(data) {
        let result = []
        let queque = data
        while (queque.length > 0) {
            result.push(queque[0].name)
            if (queque[0].children) {
                queque = queque.concat(queque[0].children)
            }
            queque.shift()
        }
        return result.join(',')
    }
    console.time('深度优先')
    console.log('深度优先', byDeepth(data))
    console.timeEnd('深度优先')

    console.time('广度优先')
    console.log('广度优先', byBreadth(data))
    console.timeEnd('广度优先')

你认为es5的设计缺陷有哪些?

可以反过来想,es6新增的特性就是为了解决es5的设计缺陷

 使用ajax轮询接口有什么优缺点?

首先,所谓轮询接口的原理是 利用 setTimeout 定时请求API接口

优点:
1,可以简单不用二次 开发websocket 实现所需功能 几乎没有学习成本
2,使用简单
缺点:
1,性能相对 socket 来说 相对差,利用 setTimeout 定时请求接口 占用内存性能 定时任务,又可能请求超时导致内存溢出
2,请求返回数据不稳定,请求失败断连 重连机制

如何提升JSON.stringify的性能

用第二个参数指定需要转换的属性,按需转换

说说你对JS中暂性死区的理解,它有什么运用场景?

var v=1;
{
    v = 2;
    let v;
}

这里候会报错: Cannot access 'v' before initialization

document.domain的作用是什么?它有什么限制?

    document.domain 能够实现部分情况下的跨域访问的问题。
    两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致
    (端口可以在两个页面都设置domain,将端口重置为null 来实现跨域)

axios为什么可以使用对象和函数两种方式调用?是如何实现的?

start


axios 源码 初始化
看源码第一步,先看package.json。一般都会申明 main 主入口文件。

// package.json
{
“name”: “axios”,
“version”: “0.19.0”,
“description”: “Promise based HTTP client for the browser and node.js”,
“main”: “index.js”,
// …
}


主入口文件

// index.js
module.exports = require(’./lib/axios’);


4.1 lib/axios.js主文件
axios.js文件 代码相对比较多。分为三部分展开叙述。

第一部分:引入一些工具函数utils、Axios构造函数、默认配置defaults等。
第二部分:是生成实例对象 axios、axios.Axios、axios.create等。
第三部分取消相关API实现,还有all、spread、导出等实现。

4.1.1 第一部分
引入一些工具函数utils、Axios构造函数、默认配置defaults等。
// 第一部分:
 

// lib/axios


// 严格模式


‘use strict’;
// 引入 utils 对象,有很多工具方法。
var utils = require(’./utils’);
// 引入 bind 方法
var bind = require(’./helpers/bind’);
// 核心构造函数 Axios
var Axios = require(’./core/Axios’);
// 合并配置方法
var mergeConfig = require(’./core/mergeConfig’);
// 引入默认配置
var defaults = require(’./defaults’);


4.1.2 第二部分
是生成实例对象 axios、axios.Axios、axios.create等。
 

/**

Create an instance of Axios

@param {Object} defaultConfig The default config for the instance
@return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// new 一个 Axios 生成实例对象
var context = new Axios(defaultConfig);
// bind 返回一个新的 wrap 函数,
// 也就是为什么调用 axios 是调用 Axios.prototype.request 函数的原因
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// 复制 Axios.prototype 到实例上。
// 也就是为什么 有 axios.get 等别名方法,
// 且调用的是 Axios.prototype.get 等别名方法。
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// 复制 context 到 intance 实例
// 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因
// 其实是new Axios().defaults 和 new Axios().interceptors
utils.extend(instance, context);
// 最后返回实例对象,以上代码,在上文的图中都有体现。这时可以仔细看下上图。
return instance;
}
// Create the default instance to be exported
// 导出 创建默认实例
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// 暴露 Axios class 允许 class 继承 也就是可以 new axios.Axios()
// 但 axios 文档中 并没有提到这个,我们平时也用得少。
axios.Axios = Axios;

// Factory for creating new instances
// 工厂模式 创建新的实例 用户可以自定义一些参数
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};


这里简述下工厂模式。axios.create,也就是用户不需要知道内部是怎么实现的。
举个生活的例子,我们买手机,不需要知道手机是怎么做的,就是工厂模式。
看完第二部分,里面涉及几个工具函数,如bind、extend。接下来讲述这几个工具方法。
4.1.3 工具方法之 bind

axios/lib/helpers/bind.js
‘use strict’;
// 返回一个新的函数 wrap
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// 把 argument 对象放在数组 args 里
return fn.apply(thisArg, args);
};
};



传递两个参数函数和thisArg指向。
把参数arguments生成数组,最后调用返回参数结构。
其实现在 apply 支持 arguments这样的类数组对象了,不需要手动转数组。
那么为啥作者要转数组,为了性能?当时不支持?抑或是作者不知道?这就不得而知了。有读者知道欢迎评论区告诉笔者呀。
关于apply、call和bind等不是很熟悉的读者,可以看笔者的另一个面试官问系列。
面试官问:能否模拟实现JS的bind方法
 

举个例子
function fn(){
console.log.apply(console, arguments);
}
fn(1,2,3,4,5,6, ‘若川’);
// 1 2 3 4 5 6 ‘若川’


4.1.4 工具方法之 utils.extend

axios/lib/utils.js
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === ‘function’) {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}


其实就是遍历参数 b 对象,复制到 a 对象上,如果是函数就是则用 bind 调用。
4.1.5 工具方法之 utils.forEach
 

axios/lib/utils.js
遍历数组和对象。设计模式称之为迭代器模式。很多源码都有类似这样的遍历函数。比如大家熟知的jQuery $.each。
/**

@param {Object|Array} obj The object to iterate
@param {Function} fn The callback to invoke for each item
*/
function forEach(obj, fn) {
// Don’t bother if no value provided
// 判断 null 和 undefined 直接返回
if (obj === null || typeof obj === ‘undefined’) {
return;
}
// Force an array if not already something iterable
// 如果不是对象,放在数组里。
if (typeof obj !== ‘object’) {
/eslint no-param-reassign:0/
obj = [obj];
}

// 是数组 则用for 循环,调用 fn 函数。参数类似 Array.prototype.forEach 的前三个参数。
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
// 用 for in 遍历对象,但 for in 会遍历原型链上可遍历的属性。
// 所以用 hasOwnProperty 来过滤自身属性了。
// 其实也可以用Object.keys来遍历,它不遍历原型链上可遍历的属性。
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}


如果对Object相关的API不熟悉,可以查看笔者之前写过的一篇文章。JavaScript 对象所有API解析
4.1.6 第三部分
 

取消相关API实现,还有all、spread、导出等实现。
// Expose Cancel & CancelToken
// 导出 Cancel 和 CancelToken
axios.Cancel = require(’./cancel/Cancel’);
axios.CancelToken = require(’./cancel/CancelToken’);
axios.isCancel = require(’./cancel/isCancel’);

// Expose all/spread
// 导出 all 和 spread API
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require(’./helpers/spread’);

module.exports = axios;

// Allow use of default import syntax in TypeScript
// 也就是可以以下方式引入
// import axios from ‘axios’;
module.exports.default = axios;


这里介绍下 spread,取消的API暂时不做分析,后文再详细分析。
假设你有这样的需求。

function f(x, y, z) {}
var args = [1, 2, 3];
f.apply(null, args);


那么可以用spread方法。用法:

axios.spread(function(x, y, z) {})([1, 2, 3]);


实现也比较简单。源码实现:

/**

@param {Function} callback
@returns {Function}
*/
module.exports = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};


上文var context = new Axios(defaultConfig);,接下来介绍核心构造函数Axios。
4.2 核心构造函数 Axios

axios/lib/core/Axios.js
构造函数Axios。
function Axios(instanceConfig) {
// 默认参数
this.defaults = instanceConfig;
// 拦截器 请求和响应拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}


 

Axios.prototype.request = function(config){
// 省略,这个是核心方法,后文结合例子详细描述
// code …
var promise = Promise.resolve(config);
// code …
return promise;
}
// 这是获取 Uri 的函数,这里省略
Axios.prototype.getUri = function(){}
// 提供一些请求方法的别名
// Provide aliases for supported request methods
// 遍历执行
// 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的是 Axios.prototype.request 方法
// 这个也在上面的 axios 结构图上有所体现。
utils.forEach([‘delete’, ‘get’, ‘head’, ‘options’], function forEachMethodNoData(method) {
/eslint func-names:0/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

utils.forEach([‘post’, ‘put’, ‘patch’], function forEachMethodWithData(method) {
/eslint func-names:0/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

module.exports = Axios;


接下来看拦截器部分。
4.3 拦截器管理构造函数 InterceptorManager

请求前拦截,和请求后拦截。
在Axios.prototype.request函数里使用,具体怎么实现的拦截的,后文配合例子详细讲述。
axios github 仓库 拦截器文档
如何使用:
// Add a request interceptor
// 添加请求前拦截器
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

// Add a response interceptor
// 添加请求后拦截器
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});

如果想把拦截器,可以用eject方法。
const myInterceptor = axios.interceptors.request.use(function () {/…/});
axios.interceptors.request.eject(myInterceptor);


拦截器也可以添加自定义的实例上。

const instance = axios.create();
instance.interceptors.request.use(function () {/…/});



源码实现:
构造函数,handles 用于存储拦截器函数。

function InterceptorManager() {
this.handlers = [];
}



接下来声明了三个方法:使用、移除、遍历。
4.3.1 InterceptorManager.prototype.use 使用
传递两个函数作为参数,数组中的一项存储的是{fulfilled: function(){}, rejected: function(){}}。返回数字 ID,用于移除拦截器。

 

/**

@param {Function} fulfilled The function to handle then for a Promise
@param {Function} rejected The function to handle reject for a Promise

@return {Number} 返回ID 是为了用 eject 移除
/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};


4.3.2 InterceptorManager.prototype.eject 移除
根据 use 返回的 ID 移除 拦截器。

 

/*

@param {Number} id The ID that was returned by use
/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
复制代码
有点类似定时器setTimeout 和 setInterval,返回值是id。用clearTimeout 和clearInterval来清除定时器。
// 提一下 定时器回调函数是可以传参的,返回值 timer 是数字
var timer = setInterval((name) => {
console.log(name);
}, 1000, ‘若川’);
console.log(timer); // 数字 ID
// 在控制台等会再输入执行这句,定时器就被清除了
clearInterval(timer);
复制代码
4.3.3 InterceptorManager.prototype.forEach 遍历
遍历执行所有拦截器,传递一个回调函数(每一个拦截器函数作为参数)调用,被移除的一项是null,所以不会执行,也就达到了移除的效果。
/*
@param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};


 

  1. 实例结合
    上文叙述的调试时运行npm start 是用axios/sandbox/client.html路径的文件作为示例的,读者可以自行调试。
    以下是一段这个文件中的代码。
axios(options)
.then(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
})
.catch(function (res) {
response.innerHTML = JSON.stringify(res.data, null, 2);
});

end


在不支持js的浏览器中如何隐藏JavaScript代码?

在<script>标签之后的代码中添加“<!-– ”,不带引号。
在</script>标签之前添加“// –->”,代码中没有引号。
旧浏览器现在将JavaScript代码视为一个长的HTML注释。而支持JavaScript的浏览器则将“<! – ”和“// – >”作为一行注释。
如:
<script><!--
alert();
// --></script>

微信的JSSDK都有哪些内容?如何接入?

微信JS-SDK:是开发者在网页上通过JavaScript代码使用微信原生功能的工具包,开发者可以使用它在网页上录制和播放微信语音、监听微信分享、上传手机本地图片、拍照等许多能力。

JSSDK使用步骤
步骤一:绑定域名
步骤二:引入JS文件
步骤三:通过config接口注入权限验证配置
步骤四:通过ready接口处理成功验证
步骤五:通过error接口处理失败验证


未完待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

短暂又灿烂的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值