js面试题

  1. 改变this指向的方法有什么?有什么区别?第一个参数是null或者是undefined那么this指向了谁?

    1. call:第一个参数:this指向,如果要传参,后面依次是参数,cal 的特点就会直接调用函数
    2. apply:参数 1 是一样的 需要改变this指向的地方,参数 2 数组,特点 第二个参数是一个数组,它自己会把数组摊开,按照下标作为实参传给对应函数的形参,函数会立刻执行
    3. bind:bind 只改变this指向 需要手动调用 ! ! !

有两种情况需要注意,传null或undefined时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global。

  1. 创建对象方法

    1. new 操作符 + Object 创建对象
    2. 字面式创建对象;如var person ={
      name: “lisi”,
      age: 21,
      family: [“lida”,“lier”,“wangwu”],
      say: function(){
      alert(this.name);
      }
      };
    3. 构造函数模式
  2. 对象有什么属性

    1. 命名数据属性:这种属性就是我们通常所用的"普通"属性,它用来将一个字符串名称映射到某个值上
    2. 命名访问器属性:getter为控制属性读取的访问器函数;控制属性写入的访问器函数称之为setter.
      属性特性:
      下面是命名数据属性拥有的特性:
      a. [[Value]] 存储着属性的值,也就是属性的数据.
      b. [[Writable]] 存储着一个布尔值,表明该属性的值是否可以改变.
      下面是命名访问器属性拥有的特性:
      a. [[Get]] 存储着getter,也就是在读取这个属性时调用的函数.该函数返回的值也就是这个属性的值.
      b. [[Set]] 存储着setter,也就是在为这个属性赋值时调用的函数.该函数在调用时会被传入一个参数,参数的值为所赋的那个新值.
      下面是两种类型的属性都有的特性:
      a. [[Enumerable]] 存储着一个布尔值.可以让一个属性不能被枚举,在某些操作下隐藏掉自己(下面会有详细讲解).
      b. [[Configurable]] 存储着一个布尔值.如果为false,则你不能删除这个属性,不能改变这个属性的大部分特性(除了[[Value]]),不能将一个数据属性重定义成访问器属性,或者反之.换句话说就是:[[Configurable]]控制了一个属性的元数据的可写性.
  3. 怎么保护对象:

es6:configurable:false

  1. 深拷贝

    (1)Js中的拷贝分为浅拷贝和深拷贝,浅拷贝是拷贝应用,深拷贝是拷贝里面的数据成新的对象
    (2)json.parse(json.stringify()) (无法实现对函数等特殊对象的clone,对象有循环会报错,会抛弃对象的constructor,所有构造函数都会指向object);
    (3)通过递归来处理不确定深度的object(包括数组)
    function deepCopy( source ) {
    if (!isObject(source)) return source; //如果不是对象的话直接返回
    let target = Array.isArray( source ) ? [] : {} //数组兼容
    for ( var k in source ) {
    if (source.hasOwnProperty(k)) {
    if ( typeof source[ k ] === ‘object’ ) {
    target[ k ] = deepCopy( source[ k ] )
    } else {
    target[ k ] = source[ k ]
    }
    }
    }
    return target
    }

function isObject(obj) {
return typeof obj === ‘object’ && obj !== null
}

  1. 定时器原理

  2. 怎么检测返回的数据数组:

    (1)Array.isArray
    (2)instanceof Array
    (3)Object.prototype.tostring.call

  3. 事件委托

利用了事件冒泡,只制定一个事件处理程序,可以管理一类型的所有事件。

  1. return break区别:

Break一般是强制退出循环,return返回调用方法

  1. json数据是什么样的

    1. 数组方式:
      [
      {“key1”: “value1”},

       {"key2": "value2"}
      

      ]

    2. 对象方式:
      {

      “key1: “value1”,
      “key2”: “value2”,

      "key3": [
      
           {"key31": "value31"},
           {"key32": "value32"}
       ]
      

    }

  2. 介绍js的基本数据类型

Undefined、Null、Boolean、Number、String、Symbol、function

  1. undefined和null的区别

    1. null这是一个对象,但是为空。因为是对象,所以 typeof null 返回 ‘object’ 。
    2. null 是 JavaScript 保留关键字。
    3. ull 参与数值运算时其值会自动转换为 0,123 + null结果值:123;123 * null结果值:0
    4. undefined是全局对象(window)的一个特殊属性,其值是未定义的。但 typeof undefined 返回 ‘undefined’ 。
    5. 尽管undefined是有特殊含义的属性,但却不是JavaScript的保留关键字。
    6. undefined参与任何数值计算时,其结果一定是NaN。
    7. undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等,即undefined == null
  2. 类型判断用到哪些方法?

typeof

注意:
1. typeof null结果是object
2. typeof [1, 2]结果是object,结果中没有array这一项,引用类型除了function其他的全部都是object
3. typeof Symbol() 用typeof获取symbol类型的值得到的是symbol,这是 ES6 新增的知识点

instanceof
用于实例和构造函数的对应。例如判断一个变量是否是数组,使用typeof无法判断,但可以使用[1, 2] instanceof Array来判断。因为,[1, 2]是数组,它的构造函数就是Array。

  1. typeof和instanceof的区别
    1. JS中会使用typeof 和 instanceof来判断一个变量是否为空或者是什么类型的。
    2. ES6规范中有7种数据类型,分别是基本类型和引用类型两大类,基本类型(简单类型、原始类型):String、Number、Boolean、Null、Undefined、Symbol;引用类型(复杂类型):Object(对象、Function、Array);
    3. typeof返回结果是该类型的字符串形式表示【6】(number、string、undefined、boolean、function、object)
    4. typeof对于原始类型来说,除了null都可以显示正确类型
    5. typeof对于对象来说,除了函数都会显示object
    6. instanceof是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。
    7. instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。之后增加了Array.isArray()方法判断这个值是不是数组的。

  2. JavaScript有几种类型的值?,你能画一下他们的内存图吗?
    这里先说两个概念:
    堆 是堆内存的简称。
    栈 是栈内存的简称。
    堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。
    javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存储在栈中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。
    javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function) …,它们是通过拷贝和new出来的,这样的数据存储于堆中。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
    栈:原始数据类型
    堆:引用数据类型

两种类型的区别是:存储位置不同;
1. 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
2. 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

在参数传递方式上,原始类型是按值传递,引用类型是按共享传递
JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。
  1. 谈谈关于本地存储和会话存储

    1. cookie
      cookie 本身不是用来做服务器端存储的,它是设计用来在服务器和客户端进行信息传递的,因此我们的每个 HTTP 请求都带着 cookie。但是 cookie 也具备浏览器端存储的能力(例如记住用户名和密码,也就是常用的登录功能),因此就被开发者用上了。使用起来也非常简单,document.cookie = …即可。cookie前端的用法,获取的时候:document.cookie;设置的时候:document.cookie = ‘key’ +’=’+value;但是 cookie 有它致命的缺点:存储量太小,只有 4KB;所有 HTTP 请求都带着,会影响获取资源的效率;所有的api请求都会携带cookie,所以cookie不太安全,使用的时候一般都需要做加密处理
      1. session
        session是服务端的会话存储技术,他的生存周期只是保持在浏览器打开,浏览器关闭这个阶段之中
      2. locationStorage
        它是专门为了浏览器端缓存而设计的。其优点有:存储量增大到 5MB;不会带到 HTTP 请求中;sessionStorage的区别就在于它是根据 session 过去时间而实现,而localStorage会永久有效
  2. 例举3种强制类型转换和2种隐式类型转换

显式类型转换是指使用函数主动将JS数据类型转化,而隐式类型转化是指在程序运行中程序自动转化类型的情况。
1. 显式类型转换的几种函数:
Number() \ parseInt() \parseFloat() \String() \toString() \ Boolean()
2. 隐式类型转化:
所有引用类型的数据,需要运算时候;先需要转化为基本类型数据;再运算。
所有引用类型数据在转化时只能先转为字符串;最后再有字符串转为其他类型。
因为浏览器默认数据就是字符串;所以只能转字符串,在其他运算符作用下转化为其他类型。

昨天面试被问到一些js基础题,虽然都能答出来,面试完自我感觉还可以,回来一查网上的答案,发现还可以说好多,可以说的更深入,最近公司不多事,杜绝摸鱼,keep learning, 向大厂冲冲冲

  1. 数组常用的方法有哪些

看了几篇博客都罗列了很多方法,但是面试的时候说不出来很多,后来看了boss上面的一篇文章,归纳的很好,分为增删改查再分别罗列,很系统方便记,很多知识不是不会,就是不系统,花点时间探索规律,让其更系统,感觉会事半功倍。

增:
a. push()
b. unshift()
c. splice()
d. concat
前三种会对原数组产生影响,concat则不会

删:

a. pop()
b. shift()
c. splice()
d. slice()
前三种会影响原数组,最后一个不会

改:
splice()
对原数组产生影响

查:
返回元素坐标或者元素值
a. indexOf()
b. includes()
c. find()

排序方法:
a. reverse()
b. sort().

转换方法:

join()
接受一个参数,即字符串分隔符,返回包含所有项的字符串

迭代方法:

迭代数组的方法都不会改变原数组
a. some()
b. every()
c. forEach()
d. filter()
e. map()

  1. 说一下箭头函数

箭头函数是ES6的API

普通函数和箭头函数的区别:

箭头函数的this指向规则:

a. 箭头函数没有prototype(原型),使用new调用箭头函数都会报错, 所以箭头函数本身没有this
b. 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this。被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
c. 不能直接修改箭头函数的this指向,幸运的是,我们可以通过间接的形式来修改箭头函数的指向:
d. 箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)
e. 箭头函数的this指向全局,使用arguments会报未声明的错误。
f. 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
g. 箭头函数不支持new.target

箭头函数相对于普通函数语法更简洁优雅:

a. 箭头函数都是匿名函数,并且都不用写function
b. 只有一个参数的时候可以省略括号:

var f = a => a; // 传入a 返回a

c. 函数只有一条语句时可以省略{}和return

var f = (a,b,c) => a; // 传入a,b,c 返回a
  1. 说一下promise

a. 是什么:

Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。

特点:

(1)对象的状态不受外界影响。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

常用法:

我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:

<div onClick={promiseClick}>开始异步请求</div>
 
const promiseClick =()=>{
	 console.log('点击方法被调用')
	 let p = new Promise(function(resolve, reject){
		//做一些异步操作
		setTimeout(function(){
				console.log('执行完成Promise');
				resolve('要返回的数据可以任何数据例如接口返回数据');
			}, 2000);
		});
        return p
	}

当放在函数里面的时候只有调用的时候才会被执行

那么,接下里解决两个问题:

a. 为什么要放在函数里面
b. resolve是个什么鬼

我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。接下来就可以用Promise对象上有then、catch方法了,这就是Promise的强大之处了,看下面的代码:

promiseClick().then(function(data){
    console.log(data);
    //后面可以用传过来的数据做些其他操作
    //......
});

先是方法被调用起床执行了promise,最后执行了promise的then方法,then方法是一个函数接受一个参数是接受resolve返回的数据这事就输出了‘要返回的数据可以任何数据例如接口返回数据’

这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在promiseClick这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

reject的用法:

以上是对promise的resolve用法进行了解释,相当于resolve是对promise成功时候的回调,它把promise的状态修改为

fullfiled,那么,reject就是失败的时候的回调,他把promise的状态修改为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
 
	promiseClick().then(
		function(data){
			console.log('resolved成功回调');
			console.log('成功回调接受的值:',data);
		}, 
		function(reason){
			console.log('rejected失败回调');
			console.log('失败执行回调抛出失败原因:',reason);
		}
	);	

catch的用法:

与Promise对象方法then方法并行的一个方法就是catch,与try catch类似,catch就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的,如下:

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
 
	promiseClick().then(
		function(data){
			console.log('resolved成功回调');
			console.log('成功回调接受的值:',data);
		}
	)
	.catch(function(reason, data){
		console.log('catch到rejected失败回调');
		console.log('catch失败执行回调抛出失败原因:',reason);
	});	

效果和写在then的第二个参数里面一样。它将大于10的情况下的失败回调的原因输出,但是,它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。如下:

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
 
	promiseClick().then(
		function(data){
			console.log('resolved成功回调');
			console.log('成功回调接受的值:',data);
			console.log(noData);
		}
	)
	.catch(function(reason, data){
		console.log('catch到rejected失败回调');
		console.log('catch失败执行回调抛出失败原因:',reason);
	});	

all的用法:

与then同级的另一个方法,all方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。

Promise.all来执行,all接收一个数组参数,这组参数为需要执行异步操作的所有方法,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面,all会把所有异步操作的结果放进一个数组中传给then,然后再执行then方法的成功回调将结果接收,结果如下:(分别执行得到结果,all统一执行完三个函数并将值存在一个数组里面返回给then进行回调输出)

这样以后就可以用all并行执行多个异步操作,并且在一个回调中处理所有的返回数据,比如你需要提前准备好所有数据才渲染页面的时候就可以使用all,执行多个异步操作将所有的数据处理好,再去渲染

race的用法:

all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调

  1. 输入url按下确定到呈现页面发生了什么

这个过程可以大致分为两个部分:网络通信和页面渲染。

网络通信(9个阶段):

a. 在浏览器中输入url
b. 应用层DNS解析域名
客户端先检查本地是否有对应的IP地址,若找到则返回响应的IP地址。若没找到则请求上级DNS服务器,直至找到或到根节点。
c. 应用层客户端发送HTTP请求
HTTP请求包括请求报头和请求主体两个部分,其中请求报头包含了至关重要的信息,包括请求的方法(GET / POST)、目标url、遵循的协议(http / https / ftp…),返回的信息是否需要缓存,以及客户端是否发送cookie等。
d. 传输层TCP传输报文
TCP协议通过“三次握手”等方法保证传输的安全可靠。

“三次握手”的过程是,发送端先发送一个带有SYN(synchronize)标志的数据包给接收端,在一定的延迟时间内等待接收的回复。接收端收到数据包后,传回一个带有SYN/ACK标志的数据包以示传达确认信息。接收方收到后再发送一个带有ACK标志的数据包给接收端以示握手成功。在这个过程中,如果发送端在规定延迟时间内没有收到回复则默认接收方没有收到请求,而再次发送,直到收到回复为止。
在这里插入图片描述
e. 网络层IP协议查询MAC地址
IP协议的作用是把TCP分割好的各种数据包传送给接收方。而要保证确实能传到接收方还需要接收方的MAC地址,也就是物理地址。IP地址和MAC地址是一一对应的关系,一个网络设备的IP地址可以更换,但是MAC地址一般是固定不变的。ARP协议可以将IP地址解析成对应的MAC地址。当通信的双方不在同一个局域网时,需要多次中转才能到达最终的目标,在中转的过程中需要通过下一个中转站的MAC地址来搜索下一个中转目标。
f. .数据到达数据链路层
在找到对方的MAC地址后,就将数据发送到数据链路层传输。这时,客户端发送请求的阶段结束
g. 服务器接收数据
接收端的服务器在链路层接收到数据包,再层层向上直到应用层。这过程中包括在运输层通过TCP协议讲分段的数据包重新组成原来的HTTP请求报文。
h. 服务器响应请求
服务接收到客户端发送的HTTP请求后,查找客户端请求的资源,并返回响应报文,响应报文中包括一个重要的信息——状态码。状态码由三位数字组成,其中比较常见的是200 OK表示请求成功。301表示永久重定向,即请求的资源已经永久转移到新的位置。在返回301状态码的同时,响应报文也会附带重定向的url,客户端接收到后将http请求的url做相应的改变再重新发送。404 not found 表示客户端请求的资源找不到。
i. 服务器返回相应文件
请求成功后,服务器会返回相应的HTML文件。接下来就到了页面的渲染阶段了。

页面渲染:

现代浏览器渲染页面的过程是这样的:jiexiHTML以构建DOM树 –> 构建渲染树 –> 布局渲染树 –> 绘制渲染树。

DOM树是由HTML文件中的标签排列组成,渲染树是在DOM树中加入CSS或HTML中的style样式而形成。渲染树只包含需要显示在页面中的DOM元素,像元素或display属性值为none的元素都不在渲染树中。

在浏览器还没接收到完整的HTML文件时,它就开始渲染页面了,在遇到外部链入的脚本标签或样式标签或图片时,会再次发送HTTP请求重复上述的步骤。在收到CSS文件后会对已经渲染的页面重新渲染,加入它们应有的样式,图片文件加载完立刻显示在相应位置。在这一过程中可能会触发页面的重绘或重排。

  1. 什么是闭包,闭包有什么优缺点

闭包:
可以访问另一个函数作用域变量的函数。由于 javascript 的特性,外层的函数无法访问内部函数的变量;而内部函数可以访问外部函数的变量(即作用域链)

由于在 Javascript 语言中,只有函数内部的子函数才能读取该函数的局部变量,因此可以 把闭包简单理解成 “定义在一个函数内部的函数”。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以在函数外部读取内部的变量,另一个就是让这些变量的值始终保持在内存中。

优点:

1.变量被保存起来没有被销毁,随时可以被调用,从而实现封装
2.只有函数内部的子函数才能读取局部变量,可以避免全局污染

缺点:

函数执行完后,函数内的局部变量没有释放,占用内存时间变长,容易造成内存泄漏
解决方法:
让内部函数变成垃圾对象,赋值为null,及时释放,让浏览器回收闭包

  1. settimeout的时间是准确的吗

不准确
因为 JavaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。
为了控制要执行的代码,就有一个 JavaScript 任务队列。
这些任务会按照将它们添加到队列的顺序执行。
setTimeout() 的第二个参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行

  1. map forecah区别,有改变原数组吗

forEach() – 对数组中的每个元素执行提供的函数
map() – 在被调用的数组基础上创建一个新数组,并对数组中的每个元素执行方法

forEach方法实际上没有返回什么东西(undefined)。它只是简单为数组中的每个元素提供一个方法。允许该回调方法改变调用的数组。
map()也为数组中的每个元素都提供了方法调用。区别在于,map()使用返回值,并实际返回和(旧)数组相同大小的新数组。

你永远不会从forEach函数的返回return值,因为返回值被抛弃。

  1. var let const的区别?let 和const可以声明同一个变量吗?

面试被问到let 和const可以声明同一个变量吗。。I dont konw…之前有知道三者的区别,一被问到,so easy…再看一些博客,还是有些区别是之前不知道的,学无止境,不要轻视基础题,基础题说出和别人不一样的答案,你就win了

a. 我们先来说说常量和变量,常量是用 const 来声明,而变量则用 var 和 let 来声明,它们最大不一样的地方就是,const 声明的常量是只读的,不可修改的,相反 let 和 var 声明的变量则是可以修改的。
b. ,我们来了解它们最复杂,也是最不同的地方,就是:作用域。我们先来分类,var 是一类,let 和 const 是另一类,前者是函数作用域,后者是区块作用域。
用 var 声明变量,它的作用域会限制在函数体内,会存在变量提升,let不会
而 let 和 const 则限制在区块内,也就是大括号内
c. let 和 const 必须在严格模式下才可以使用哦,也就是我们常在文件头放的那个 use strict;

let 和const不可以声明同一个变量

  1. 请求太快,loading一闪而过用户体验不好,如何延长loading时间?

加个锁。

var requestTime = Date.now()

$.getJSON(’’, function (response) {

const responseTime = Date.now()

if (responseTime - requestTime < 5000) {

setTimeout(function() {

  stopLoading()

}, 5000 - (responseTime - requestTime));

return

}

stopLoading()

})

这样可以保证起码loading 5s。具体时间自己改一下就行

  1. 说一下怎么部署easy-mock?

a. 安装nvm管理多node版本
因为easy-mock 仅支持node8.x版本 所以需要同时存在低版本node
nvm是用于管理多个node版本的工具。
安装:
点击下载地址进入nvm下载页面;
选择最新版本,进去之后选择nvm-setup.zip安装版,下载之后解压安装即可;# 更换淘宝源
nvm node_mirror https://npm.taobao.org/mirrors/node/
开启nvm
nvm on

b. MongoDB安装
安装包安装之后一直下一步,我这里是安装到D:\MongoDB目录下,根据自己情况自行更改;

主要有下面几步:

D:\MongoDB\data下面新建一个文件夹db
​ D:\MongoDB\log下面新建一个文件mongo.log

创建服务 把可执行文件的D:\MongoDB\bin添加到系统变量里;

管理员权限的cmd中注册服务:mongod --config “D:\MongoDB\mongo.conf” --install --serviceName “MongoDB”

​ cmd中开启服务:net start mongodb

​ 这时候浏览器中访问127.0.0.1:27017应该就已经有内容了

进入mongo环境
​ 如果之前安装无误的话,cmd中输入mongo应该就可以进入mongo的可执行环境了,这时输入db应显示test

mongo运行环境下:use easymockdb
c. redis安装

Redis类似,在Github-release下载一个msi版本安装,一直下一步;

添加路径

添加安装路径D:\Redis到系统变量里

创建服务

cmd下redis-server redis.windows.conf

安装为windows服务
安装为windows服务后 可以关闭命令行依然启动服务

​ cmd中注册服务:redis-server --service-install redis.windows-service.conf --loglevel verbose

​ 注册后需要开启服务 redis-server --service-start

常用Redis命令:

卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop

d. 安装部署Easy-Mock

具体安装从git上clone下来并install、build

git clone https://github.com/easy-mock/easy-mock.git
cd easy-mock
npm install
npm run build
npm run start

这时候访问本地的 http://localhost:7300 就可以打开Easy-Mock页面了,跟Easy-Mock官网一样的~

  1. let 和const实现原理是什么?

JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引擎会在合适的时机进行 GC,回收旧的内存空间。

const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。

let:解析器进入一个块级作用域,发现let关键字,变量只是先完成声明,并没有到初始化那一步。此时如果在此作用域提前访问,则报错xx is not defined,这就是暂时性死区的由来。等到解析到有let那一行的时候,才会进入初始化阶段。如果let的那一行是赋值操作,则初始化和赋值同时进行

比如解析如下代码步骤:

{
// 没用的第一行
// 没用的第二行
console.log(a) // 如果此时访问a报错 a is not defined
let a = 1
}

步骤:
发现作用域有let a,先注册个a,仅仅注册
a is not defined,暂时性死区的表现
假设前面那行不报错,a初始化为undefined
a赋值为1

对比于var,let、const只是解耦了声明和初始化的过程,var是在任何语句执行前都已经完成了声明和初始化,let、const仅仅是在任何语句执行前只完成了声明

  1. 说一下原型链,prototype和_proto_的区别?

原型

a. 所有的引用类型都有一个’_ _ proto_ '属性(也叫隐式原型,它是一个普通的对象)。
b. 所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。
c. 所有引用类型,它的’
_ proto_ '属性指向它的构造函数的’prototype’属性。
d. 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’
_ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。

原型链

原型链的核心就是依赖对象的_proto_的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有_proto_指向了。

属性搜索原则:
当访问一个对象的成员的时候,会现在自身找有没有,如果找到直接使用。
如果没有找到,则去原型链指向的对象的构造函数的prototype中找,找到直接使用,没找到就返回undifined或报错。

在这里插入图片描述

  1. 常见的继承的方法

a. 第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){

    Animal.apply(this, arguments);

    this.name = name;

    this.color = color;

  }

  var cat1 = new Cat("大毛","黄色");

  alert(cat1.species); // 动物

b. 原型链继承法

把要继承的函数放到继承者的原型中,原型对象的属性是共享的

		function parent(){
			this.a = 10;
			this.b = 20;
		}
		function child2(){
			this.c = 30;
		}
		child2.prototype = new parent();
 
		var c = new child2();
		console.log(c);console.log(c.a)

c. 利用空对象作为中介

上面原型链的继承存在的问题,缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

var F = function(){};

  F.prototype = Animal.prototype;

  Cat.prototype = new F();

  Cat.prototype.constructor = Cat;
  1. async和promise的原理以及区别?

async/await原理:generator函数

通过next()执行,如果没有则不执行。
也可以通过next修改属性。
yield只能在generator函数里。
遇到return直接返回最后的值,只能返回一个值。
yield表示暂停执行,next方法表示回复执行。
async函数就是generator的语法糖,只不过generator需要自己写执行器。async/await可以看成带自动启动器的generator函数的语法糖。
async/await只支持promise对象和原始值,不支持thunk函数。

promise原理:发布订阅模式

promise的核心原理其实就是发布订阅模式,通过两个队列来缓存成功的回调(onResolve)和失败的回调(onReject)

简易版的发布订阅模式:

let dep = {
  list: [],
  on: function (fn) {
    list.push(fn);
  },
  emit: function () {
    this.list.forEach(event => {
      typeof event === 'function' ? event() : null;
    })
  }
};
复制代码

两者区别:

处理结果上:promise需要使用.then()来处理promise返回的结果,而async/await则直接在代码上顺序处理结果。
错误处理:promise如果在then里出现异常,只能用promise的catch函数来捕获,外面的try/catch捕获不到。async/await可以同时捕获异步和同步代码抛出的异常。

  1. promise.all 和promise.allSettled的区别?

Promise.allSettled永远不会被reject
当用Promise.allSettled时,我们只需专注在then语句里,当有promise被异常打断时,我们依然能妥善处理那些已经成功了的promise,不必全部重来

  1. for in 和for of 的区别

for…of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()不同的是,它可以正确响应break、continue和return语句

for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用for-in循环(这也是它的本职工作)或内建的Object.keys()方法:

for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

遍历对象 通常用for in来遍历对象的键名

  1. 常见的循环遍历方法?

a. for in 循环

for in 循环主要用于遍历普通对象,i 代表对象的 key 值,obj[i] 代表对应的 value

但是遍历数组时候,要注意,因为 i 输出为字符串形式,而不是数组需要的数字下标,这意味着在某些情况下,会发生字符串运算,导致数据错误,比如:‘52’+0 = ‘520’ 而不是我们需要的 52。

另外 for in 循环的时候,不仅遍历自身的属性,还会找到 prototype 上去,所以最好在循环体内加一个判断,就用 obj[i].hasOwnProperty(i),这样就避免遍历出太多不需要的属性。

b. forEach 循环

forEach循环,循环数组中每一个元素并采取操作, 没有返回值, 可以不用知道数组长度,他有三个参数,只有第一个是必需的,代表当前下标下的 value。

forEach 循环在所有元素调用完毕之前是不能停止,但可以尝试 try catch 语句,就是在要强制退出的时候,抛出一个 error 给 catch 捕捉到,然后在 catch 里面 return,这样就能中止循环了,如果你经常用这个方法,最好自定义一个这样的 forEach 函数在你的库里。

c. map()方法

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map 和 forEach 方法都是只能用来遍历数组,不能用来遍历普通对象。

d. Array reduce()方法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

let arr = [1,2,3];

let ad = arr.reduceRight(function(i,j){

 return i+j;

})

// 6  

e. filter() 方法

是数组对象的内置方法,它会返回通过过滤的元素,不改变原来的数组

let arr = [1,2,3];

let tt = arr.filter(function(i){

 return i>1;  //遍历数组中是所有元素返回大于1的元素

})

// [2,3]

f. Array some() 方法

some() 方法用于检测数组中的元素(只要有一个满足条件就是true)是否满足指定条件(函数提供),返回 boolean 值,不改变原数组。

  1. 多次快速请求怎么做到只执行最后一个?

同一个方法里的的多个ajax异步请求,由于请求是并行的,执行的快与慢,要看响应的数据量的大小及后台逻辑的复杂程度。而且有一个现象是:首先获得响应的不一定是先触发的请求。

我的思路是用闭包记录执行次数,并同时记录发起ajax的次数,等数据返回的时候比较两次次数的结果,渲染最后一次数据

<script>
    //定义点击次数和方法执行次数
    var a = 1;
    var flag = 1;
    $('#ajaxbtn').click(function () {
        a = a + 1
        $('#num').html(a)
        console.log(a);
        btnAjax('https://api.douban.com/v2/movie/in_theaters', cb);
    })
//封装ajax事件
    function btnAjax(url, cb) {


        $.ajax({
            type: 'get',
            url: url,
            dataType: 'jsonp',
            success: function (data) {
                var func = callbackFunc(data, cb);
                func()
            }
        })
    }
    //返回函数
    function cb(data) {
        console.log(a);
        console.log(data);
        var str = '';
        for (var i = 0; i < data.subjects.length; i++) {
            str += '<img src="' + data.subjects[i].images.small + '">';
        }
        $('#show').html(str)
    }
    //判断次数,获取返回函数
    function callbackFunc(data, cb) {
        flag++;
        if (a == flag) {
            return function () {
                cb(data);
            }
        } else {
            return function () {
            }
        }
    }
</script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值