前端面试知识点大全——JS篇(二)

总纲:前端面试知识点大全

目录

1.闭包 与 作用域、作用域链、执行环境

1.1 闭包

1.2 执行环境(执行上下文)

1.3 作用域

1.4 作用域链

1.5 LHS(left-hand side)和RHS(right-hand side)查询

1.6 小结

2.匿名函数

3.你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?

4.请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

5.apply call bind

5.1 区别

5.2 实现原理

5.3 手动实现

6.new操作符

7.document.write() 与 innerHTML

8.特性检测 特性推断 UA字符串嗅探

8.1 能力检测(也称为特性检测)

8.2 怪癖检测

8.3 用户代理检测

9.Ajax工作原理

9.1 作用

9.2 使用

9.3 响应数据格式

9.4 state表示xhr的状态

9.5 status表示http状态码

9.6 优点

9.7 缺点

10.跨域

10.1 图片ping

10.2 JSONP

10.3 CORS(跨域资源共享 )

10.4 document.domain

10.5 window.name

10.6 postMessage


1.闭包 与 作用域、作用域链、执行环境

1.1 闭包

《JS高程》定义:有权访问另一个函数作用域内变量的函数都是闭包。通俗说法,可以访问其他函数内部变量的函数。

闭包的作用:

1、函数柯里化的核心

2、匿名自执行函数(可以访问全局对象,同时有不会污染全局)

3、缓存结果(父函数的变量不会释放)

4、代码封装

5、实现类和继承

闭包的缺点:

(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

1.2 执行环境(执行上下文)

《JS高程》中的解释是:执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都至少有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

通俗的说,所谓的执行环境,可以看做代码当前运行的环境。

全局执行环境(上下文)是最外围的一个执行环境。在web浏览器中,全局执行环境指的是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

某个执行环境的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到应用程序退出(例如关闭网页或浏览器)时才会被销毁。

执行环境会随着函数的调用和返回,不断的重建和销毁。但变量对象在有变量引用(如闭包)的情况下,将留在内存中不被销毁。整个变量对象不销毁,而不是某个变量不销毁。

三类执行环境(上下文):

1、全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,js引擎最先进入的就是这个环境

2、函数级别的代码 – 当执行一个函数时,运行函数体中的代码

3、Eval的代码 – 在Eval函数内运行的代码(这个不常使用,也不推荐使用,故不作了解)

PS:不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。函数上下文的个数是没有任何限制的,每到调用执行一个函数时,js 引擎就会自动新建出一个函数上下文。在外部的上下文中是无法直接访问到其内部上下文里的变量的,但是内部上下文可以访问到外部上下文中的的变量。

存在一个执行环境栈,全局执行环境肯定是在最底部。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

参考链接:https://blog.csdn.net/iamchuancey/article/details/78230791

1.3 作用域

ES5只有函数作用域和全局作用域,没有块级作用域,ES6引入let和const,有了块级作用域。一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。其本身是一套规则,用于确定在何处以及如何查找标识符,规定了函数和变量可使用的位置

1.4 作用域链

《JS高程》:作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

保证对执行环境有权访问的所有变量和函数的有序访问。每个函数都有一个执行环境,定义了变量或函数有权访问的其他数据,与之关联的就是一个变量对象,这个对象中包含了当前函数可访问的变量及函数。当代码在执行环境中执行时,就形成了作用域链。而作用域链就是指向这些变量对象的一个指针列表,他的最前端指向的就是当前执行环境的变量对象,最后面指向window全局执行环境的变量对象。

作用域链的延长:with会将对象绑定到作用域链的顶端,try...catch语句中的catch捕获到的对象会被放到作用域顶端,离开的时候会自动销毁。

作用域的作用:使用作用域链主要是进行标识符的查询,标识符解析就是沿着作用域链一级一级地搜索标识符的过程,而作用域链就是要保证对变量和函数的有序访问

1.5 LHS(left-hand side)和RHS(right-hand side)查询

在引擎执行的第一步操作中,对变量a进行了查询,这种查询叫做LHS查询。实际上,引擎查询共分为两种:LHS查询和RHS查询 。

从字面意思去理解,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。

更准确地讲,RHS查询与简单地查找某个变量的值没什么区别,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。

1.6 小结

1、执行环境有全局执行环境(也称为全局环境)和函数执行环境之分

2、每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链

3、函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境,沿作用域链向外访问变量

4、全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据

5、变量的执行环境有助于确定应该合适释放内存

【1】javascript使用的是词法作用域。对于函数来说,词法作用域是在函数定义时就已经确定了,与函数是否被调用无关。通过作用域,可以知道作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态的

【2】对于函数来说,执行环境是在函数调用时确定的,执行环境包含作用域内所有变量和函数的值。在同一作用域下,不同的调用(如传递不同的参数)会产生不同的执行环境,从而产生不同的变量的值。所以执行环境是动态的

深入理解javascript作用域系列第五篇:https://www.cnblogs.com/xiaohuochai/p/5722905.html

js作用域与执行环境:https://www.cnblogs.com/ukerxi/p/8027236.html

 

2.匿名函数

在js中分为匿名函数和命名函数,匿名函数常配合闭包和IIFE使用,用于减少全局变量的污染。也可以用变量绑定匿名函数,这样变量就会指向匿名函数。

有一种特殊情况 const func = function bar(){};

这种情况下外部使用bar()会是未定义,bar主要用于函数内部使用,比如递归。

3.你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?

内部一些模块或者组件使用模块的方式,外部调用和正常使用时使用继承的方式。

4.请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

第一个是声明定义一个Person函数

第二个是直接调用Person()函数,并将返回值赋值给变量person

第三个是创建了一个Person实例对象

5.apply call bind

5.1 区别

1、apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;

2、apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;

3、apply 、 call 、bind 三者都可以利用后续参数传参,call和bind一个一个写,apply传入数组;

4、bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

5.2 实现原理

apply 和call 是返回执行后的结果,所以在要绑定对象的对象内绑定一个函数执行即可。参数使用arguments导出一个数组将剩余的参数传入。

1. 将函数设为对象的属性

2. 执行该函数

3. 删除该函数

(注:arguments是一个类似数组的东西,只有索引和长度。在不手动的情况下可以使用Array.prototype.slice.call(arguments);但是这里是手动情况所以要一点点读取导出。)

bind 的话,对于执行上面使用eval直接运算函数。返回函数的时候使用一个闭包

参考博文:https://blog.csdn.net/qq_40479190/article/details/78324270

5.3 手动实现

call和apply就是将函数加入到绑定的对象中,然后在这个对象中执行函数。

比如:bar.call(foo),将this绑定到foo对象上,执行bar方法,就相当于,

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value);
    }
};
foo.bar();

call的模拟实现:

Function.prototype.myCall = function(context) {
    //call方法,第一项可以是null或者没有,默认指向window
    var context = context || window;
    //获取调用call的函数,用this获取
    context.fn = this;
    //存放后面的数组
    var args = [];
    //可以ES5的语法,args = Array.prototype.slice.call(arguments, 1);
    for(var i=1,len=arguments.length;i<len; i++) {
        //只有变成字符串,在eval时才能转成代码执行
        args.push('arguments['+ i +']');
    }

    //直接调用toString方法
    var result = eval('context.fn('+ args +')');
    delete context.fn;
    return result;
};

apply的模拟实现:

Function.prototype.myApply = function(context, argsArr) {
    //call方法,第一项可以是null或者没有,默认指向window
    var context = context || window;
    //获取调用call的函数,用this获取调用方法的对象(这里是函数调用的apply,所以this指向函数本身,函数也是对象)
    context.fn = this;
    var result;
    if (!argsArr) {
        result = context.fn();
    } else {
        var args = [];
        for(var i = 0, len = argsArr.length; i < len; i++) {
            args.push('argsArr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result;
};

bind的模拟实现:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this之后的序列参数将会在传递的实参前传入作为它的参数。

Function.prototype.myBind2 = function(context) {
    var aArgs = Array.prototype.slice.call(arguments, 1);
    var that = this;
    return function() {
        return that.apply(context, aArgs.concat(Array.prototype.slice.call(arguments)))
    }
}

6.new操作符

一个new操作符的内部行为,比如let func = new Func(params);

第一步:新建一个空对象,let obj=new Object();

第二步:为这个新建对象设置原型,继承构造函数的原型 obj.__proto__ = Func.prototype;//有可能是基本类型

第三步:使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。let result = Func.call(obj,params); 执行构造函数并绑定this

第四步:判断构造函数的返回类型,基本类型或无返回值就返回obj,是对象就返回result,即return typeof result === 'object' ? result : obj;

上述步骤具体等同于:

new Animal('cat') = {
    var obj = {};
    obj.__proto__ = Animal.prototype;
    var result = Animal.call(obj,"cat");
    return typeof result === 'object'? result : obj;
}

7.document.write() 与 innerHTML

document.write()用于写入文档内容,可以传入多个字符串,写入的字符串会被HTML解析;返回undefined

注意事项:

1、如果document.write()在DOMContentLoaded或load事件的回调函数中,当文档加载完成,则会先清空文档(自动调用document.open()),再把参数写入body内容的开头。也就是说原文档内容会被清除

2、在异步引入的js和DOMContentLoaded或load事件的回调函数中运行document.write(),运行完后,最好手动关闭文档写入(document.close())。异步引用外部js文件,JS文件中必须先运行document.open()清空文档,然后才能运行document.write(),参数写在body内容的开头。如果不先运行document.open(),直接运行document.write(),则无效。

 

document.write() 只能重新渲染绘制整个页面

innerHTML 可以回流页面的一部分

8.特性检测 特性推断 UA字符串嗅探

8.1 能力检测(也称为特性检测)

能力检测是识别浏览器的能力,只需要确定浏览器支持特定的能力,就能给出解决方案

通用方法检测默写方法和属性是否可用或者存在,其次利用typeof进行能力检测

8.2 怪癖检测

也就是bug检测,如果有bug就先修复

8.3 用户代理检测

navigator.useAgent属性访问,可以获取用户代理字符串。这种方法优先级最靠后,因为可以欺骗。

例如:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36

9.Ajax工作原理

参考:https://segmentfault.com/a/1190000004322487

https://blog.csdn.net/sinat_36521655/article/details/79986394

9.1 作用

局部刷新页面。就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息。

9.2 使用

const xhr = new XMLHttpRequest() //或者IE情况下使用ActiveXObject('Microsoft.XMLHttp')
xhr.open('POST','ajax.php',true);//默认就是async异步
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send("fname=suifeng&lname=nifeng");

//异步
xhr.onreadystatechange = function () {
    if(xhr.readyState === 4 && xhr.status === 200){
        document.getElementById("id").innerHTML = xhr.responseText;
    }
}

//同步
xhr.open('GET','ajax.txt',false);
xhr.send(null);
document.getElementById("id").innerHTML = xhr.responseText;

9.3 响应数据格式

responseText:获得字符串形式的响应数据(DOMString)

responseXML:获得XML形式的响应数据(document)

9.4 state表示xhr的状态

0:请求未初始化,尚未调用open()方法

1:服务器连接已建立,已调用open(),尚未调用send()

2:请求已接收,已调用send(),尚未收到响应

3:请求处理中,已经接收部分响应数据

4:请求已完成,且响应已就绪。数据全部接收,可以使用

9.5 status表示http状态码

200:“ok”

204:无内容

301:(永久重定向)

302:(临时重定向)

304:(未修改)服务器资源未修改,http响应不会返回网页内容

403:(禁止)服务器拒绝请求

404:(未找到)服务器找不到请求的资源

500:(服务器内部错误)服务器遇到错误,无法完成请求

502:(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。

503:(服务不可达)服务器维护或者过载,无法发完成请求

504:(网关超时)服务器作为网关或代理,但没有及时从上游服务器收到请求

9.6 优点

1、最大的一点是页面无刷新,用户的体验非常好。

2、使用异步方式与服务器通信,具有更加迅速的响应能力。

3、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。

4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

9.7 缺点

1、ajax不支持浏览器back按钮。

2、安全问题 AJAX暴露了与服务器交互的细节。

3、对搜索引擎的支持比较弱。

4、破坏了程序的异常机制。

5、不容易调试。

10.跨域

https://www.cnblogs.com/2050/p/3191744.html

跨域请求:从一个域的网页去请求另一个域的资源,实现不同的域之间进行数据传输或通信。协议、域名、端口有任何一处不一样都被视为跨域。

PS:1、如果是协议和端口造成的跨域问题,前端是无能为力的;2、在跨域问题上,域仅仅是通过“URL的首部(协议、域名、端口)”来识别,而不会根据域名对应的IP地址是否相同来判断。(也就是IP地址没用)

10.1 图片ping

它是与服务器进行简单、单项跨域通信的一种方式。请求的数据是通过查询字符串形式发送的(比如http://www.example.com/test.html?name=Jhon),他的响应通常书像素图或者204(无内容)响应,通过监听load和error事件,可以知道响应是什么时候收到的。

var img = new Image();
img.onload = img.onerror = function () {
    console.log("receive");
};
img.src = "http://www.example.com/test.html?name=Jhon";

用途:图像ping最常用于跟踪用户点击页面或者动态广告曝光次数。

缺点:1、只能发送get请求;2、无法访问服务器的响应文本

10.2 JSONP

JSON with padding(填充式JSON或参数式JSON)。通过动态生成script标签,然后添加src,产生get请求,且不会发生跨域问题。通过get请求添加callback参数的方式,后台返回由callback函数包裹JSON数据的方式传回给前端,这个代码会在前端作为js代码直接执行,只要前端这边提前写好了callback函数即可。(需要后台配合)

优点:兼容性更好,可以在旧浏览器中运行,在请求完毕后可以通过调用callback的方式回传结果

缺点:1、只支持get请求;2、安全问题(请求返回的代码中可能存在安全隐患,需要字符串过滤?);3、要确定jsonp请求是否失败并不容易;4、需要商量基础token作为身份验证,不然是个网站都可以拿到网站的数据。

10.3 CORS(跨域资源共享 )

请求头部设置Origin:URL,响应头部设置Acess-Control-Access-Origin:URL(或者*,表示所有的都行),两个URL相同就可以实现跨域访问了。

https://www.cnblogs.com/loveis715/p/4592246.html

10.4 document.domain

document.domain="example.com"的方式进行修改二级域名,实现后两个域名相同的网页通信。一般适用于不同子域的框架之间的通信,这样可以获取别的框架页面的脚本的对象和属性

如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe(利用iframe框架的contentWindow属性可以获取该页面window对象,里面包含了页面内的所有活动属性和对象),这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

10.5 window.name

window.name:window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

10.6 postMessage

postMessage:跨文档消息则是通过向Window实例发送消息来完成的。在使用时,软件开发人员需要通过调用一个Window的postMessage()函数来向该Window实例发送消息。此时Window实例内部的onmessage事件将被触发,进而使得该事件的消息处理函数被调用。但是在接收到消息的时候,消息处理函数首先需要判断消息来源的合法性,以避免恶意用户通过发送消息的方式来非法执行代码。otherWindow.postMessage(message, targetOrigin, [transfer]);

 

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值