admin 管理员组

文章数量: 887021

前端面试题

  1. ==和===的区别?
    比较过程:
      双等号==:
      (1)如果两个值类型相同,再进行三个等号(===)的比较
      (2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
        1)如果一个是null,一个是undefined,那么相等
        2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
      
      三等号===:
      (1)如果类型不同,就一定不相等
      (2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)
      (3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
      (4)如果两个值都是true,或是false,那么相等
      (5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等
      (6)如果两个值都是null,或是undefined,那么相等

  2. float/postion属性
    Float CSS属性指定一个元素应沿其容器的左侧或右侧放置,允许文本和内联元素环绕它。该元素从网页的正常流动(文档流)中移除,尽管仍然保持部分的流动性(与 绝对定位 相反)。

positon属性:

  • 静态定位是默认行为
  • 相对定位是我们将要看的第一个位置类型。 它与静态定位非常相似,占据在正常的文档流中,除了你仍然可以修改它的最终位置,包括让它与页面上的其他元素重叠。
  • 绝对定位的元素不再存在于正常文档布局流中。相反,它坐在它自己的层独立于一切。这是非常有用的:这意味着我们可以创建不干扰页面上其他元素的位置的隔离的UI功能 。例如,弹出信息框和控制菜单;翻转面板;可以在页面上的任何地方拖放的UI功能……
  • 固定定位:绝对定位固定元素是相对于 元素或其最近的定位祖先,而固定定位固定元素则是相对于浏览器视口本身。 这意味着您可以创建固定的有用的UI项目,如持久导航菜单。
  1. 闭包注意点
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return I * I;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9
  1. JS事件委托
    JavaScript事件代理:
    当我们需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数。这主要得益于浏览器的事件冒泡机制

Js高级程序上的定义:利用事件冒泡,只绑定一个函数就可以处理一类事件。

[image:14F64785-543A-43E1-974D-5E177FBB7491-1487-0000250FEBE5AC06/屏幕快照 2019-05-09 23.31.43.png]
实例:
Html

<!-- 没有给p元素设置data-name,点击p元素时会显示data-name为null -->
    <div id="grandFather" data-name="grandFather" style="width: 700px;height: 700px; background-color: red">
        <p>grandFather</p>
        <div id="father1" data-name="father1" style="width: 300px; height: 300px; background-color: pink">
            <p>father1</p>
            <div id="son1" data-name="son1" style="width: 100px; height: 100px; background-color: yellow">
                <p>son1</p>
            </div>
        </div>
        <div id="father2" data-name="father2" style="width: 300px;height: 300px; background-color: green">
            <p>father2</p>
        </div>
    </div>

Js

// 事件代理
 grandFather.addEventListener('click', function(event){
            console.log('I am ' + event.target.getAttribute('data-name'));
        },false);

事件代理的好处:

  • 优化性能
  • 当新元素绑添加进来的时候不需要再次绑定事件,通过冒泡就可以触发。
  1. 浏览器输入url到页面呈现出来发生了什么?

    1. 进行地址解析
      1. 解析出字符串地址中的主机,域名,端口号,参数等
    2. 根据解析出的域名进行DNS解析
      1. 首先在浏览器中查找DNS缓存中是否有对应的IP地址,如果有就直接使用,没有机执行第二步
      2. 在操作系统中查找DNS缓存是否有对应的IP地址,如果有就直接使用,没有就执行第三步
      3. 向本地DNS服务商发送请求查找时候有DNS对应的ip地址。如果仍然没有最后向Root Server服务商查询。
    3. 根据查询到的IP地址寻找目标服务器
      1. 与服务器建立连接
      2. 进入服务器,寻找对应的请求
    4. 浏览器接收到响应码开始处理。
    5. 浏览器开始渲染DOM,下载CSS、图片等一些资源。直到这次请求完成
      实例:
  2. 清除浮动
    1.父级div定义 height
    原理:父级div手动定义height,就解决了父级div无法自动获取到高度的问题。
    优点:简单、代码少、容易掌握
    缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题

2.父级div定义 overflow:hidden

原理:必须定义width或zoom:1,同时不能定义height,使用overflow:hidden时,浏览器会自动检查浮动区域的高度
优点:简单、代码少、浏览器支持好

4.结尾处加空div标签 clear:both
原理:添加一个空div,利用css提高的clear:both清除浮动,让父级div能自动获取到高度
优点:简单、代码少、浏览器支持好、不容易出现怪问题
缺点:不少初学者不理解原理;如果页面浮动布局多,就要增加很多空div,让人感觉很不好

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
  1. 请描述一下 cookies sessionStorage和localstorage区别
    相同点:都存储在客户端
    不同点:

    1. 存储大小
      cookie数据大小不能超过4k。
      sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
    2. 有效时间
      localStorage:存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
      sessionStorage:数据在当前浏览器窗口关闭后自动删除。
      cookie:设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
    3. 数据与服务器之间的交互方式
      cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端
      sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
  2. 计算一个数组arr所有元素的和

var sum = function(arr) {
	return arr.reduce(function (x, y) {
        return x + y;
	});
}
sum(arr)
  1. 编写一个方法去掉数组里面的重复的内容 var arr=[1,2,3,4,5,1,2,3]

方法一:filter() + indexOf()

function norepeat(arr){
	return arr.filter(function(val, index, array) {
		return array.indexOf(val)===index;
	});
}

方法二: ES6 Set

function norepeat(arr){
    return [new Set(arr)];
}

方法三: 方法二:用sort() 然后相邻比较也可以实现

  1. document.write和innerHTML的区别
    document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。

innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。

  1. Ajax
    定义:
    Asynchronous JavaScript + XML(异步JavaScript和XML), 其本身不是一种新技术,而是一个在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的‘新’方法。包括: HTML or XHTML , Cascading Style Sheets , JavaScript , The Document Object Model , XML , XSLT , 以及最重要的 XMLHttpRequest object 。当使用结合了这些技术的AJAX模型以后, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。
    尽管X在Ajax中代表XML, 但由于 JSON 的许多优势,比如更加轻量以及作为Javascript的一部分,目前JSON的使用比XML更加普遍。JSON和XML都被用于在Ajax模型中打包信息。

原理:
[image:0657ED56-649F-4643-A246-CDB31F0A876D-1576-000010D64F5B8229/ajax.gif]

步骤:

  • 第一步,创建xmlhttprequest对象,var xmlhttp =new XMLHttpRequest();XMLHttpRequest对象用来和服务器交换数据。
varxhttp;
if(window.XMLHttpRequest) {
//现代主流浏览器
xhttp = newXMLHttpRequest();
} else{
// 针对浏览器,比如IE5或IE6
xhttp = newActiveXObject("Microsoft.XMLHTTP");
}
  • 第二步,使用xmlhttprequest对象的open()和send()方法发送资源请求给服务器。
xmlhttp.open(method,url,async):
	method包括get 和post,
	url主要是文件或资源的路径,
	async参数为true(代表异步)或者false(代表同步)
xhttp.send():使用get方法发送请求到服务器。
xhttp.send(string):使用post方法发送请求到服务器。
  • 第三步,使用xmlhttprequest对象的responseText或responseXML属性获得服务器的响应。

  • 第四步,onreadystatechange函数,当发送请求到服务器,我们想要服务器响应执行一些功能就需要使用onreadystatechange函数,每次xmlhttprequest对象的readyState发生改变都会触发onreadystatechange函数

实例:

function loadXMLDoc()
{
	//	第一步
	var xmlhttp;
	if (window.XMLHttpRequest)
	{
		//  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
		xmlhttp=new XMLHttpRequest();
	}
	else
	{
		// IE6, IE5 浏览器执行代码
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}
	//	第四步
	xmlhttp.onreadystatechange=function()
	{
		if (xmlhttp.readyState==4 && xmlhttp.status==200)
		{
	//	第三步	
	document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
		}
	}
	//	第二步
	xmlhttp.open("GET","/try/ajax/ajax_info.txt",true);
	xmlhttp.send();
}
  1. XML和JSON的区别
    JSON相对于XML来讲,数据的体积小,传递的速度更快些
    JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
    XML对数据描述性比较好;
    JSON的速度要远远快于XML

  2. box-sizing常用的属性有哪些?分别有什么作用?
    content-box(默认):width即内容显示部分的宽度,width = content
    border-box:即为元素在设置内边距和边框是在已经设定好的宽度和高度之内进行绘制,width = margin + border + padding + content

  3. css选择器有哪些,选择器的权重的优先级
    选择器类型

1、ID  #id
2、class  .class
3、标签  p
4、通用  *
5、属性  [type=“text”]
6、伪类  :hover
7、伪元素  ::first-line
8、子选择器、相邻选择器

三、权重计算规则
1、第一等:代表内联样式,如: style=””,权值为1000。
2、第二等:代表ID选择器,如:#content,权值为0100。
3、第三等:代表类,伪类和属性选择器,如.content,权值为0010。
4、第四等:代表类型选择器和伪元素选择器,如div p,权值为0001。
5、通配符、子选择器、相邻选择器等的。如*、>、+,权值为0000。
6、继承的样式没有权值。

  1. 块级元素水平垂直居中的方法有哪些(三个方法)
    方法一:负margin
.mycss{
	width: 300px;
	height: 200px;
	position: absolute;
	left: 50%;
	top: 50%;
	margin: -100px 0 0 -150px;
}

方法二:利用CSS的margin设置为auto让浏览器自己帮我们水平和垂直居中

.mycss{
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	margin: auto;
	height: 200px;
	width: 300px;
}

方法三:弹性盒子

.mycss{
	display: flex;
	align-item: center;
	justify-content: center;
}
  1. 三个盒子,左右定宽,中间自适应有几种方法
  • 第一种:左右侧采用浮动 中间采用margin-left 和 margin-right 方法。
<div style="width:100%; margin:0 auto;">
	<div style="width:200px; float:right; background-color:#960">这是右侧的内容 固定宽度</div>
	<div style="width:150px; float:left; background:#6FF">这是左侧的内容 固定宽度</div>
	<div style="margin-left:150px;margin-right:200px; background-color:#9F3">中间内容,自适应宽度</div>
	</div>
  • 第二种:左右两侧采用绝对定位中间同样采用margin-left margin-right方法

  • 第三种:用弹性布局flex:

<style>
   /* 弹性盒子布局*/
	.wrap{
		width: 100%;
		height: 90px;
		display: flex;
	}
	.left{
		width: 300px;
		background-color: red;
		float: left;
	}
	.content{
		flex:1;
		height: 90px;
		background-color: yellow;
	}
.right{
		width: 300px;
		height: 90px;
		background-color:blue;
		float: right;
	}
    </style>
</head>
<body>
<div class="wrap">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
  1. js有几种数据类型,其中基本数据类型有哪些
    5种基本类型:Undefined、Null、Boolean、Number和String。
    还有1种复杂的数据类型:Object。Object本质上是由一组无序的名值对组成的。
  2. undefined 和 null 区别
  • javaScript权威指南:
    null 和 undefined 都表示“值的空缺”,你可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,而null是表示程序级的、正常的或在意料之中的值的空缺。
  • javaScript高级程序设计:
    在使用var声明变量但未对其加以初始化时,这个变量的值就是undefined。 null值则是表示空对象指针。
  1. http 和 https 有何区别?如何灵活使用?
    http是HTTP协议运行在TCP之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。

https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份

  1. 常见的HTTP状态码
  • 2开头 (请求成功)表示成功处理了请求的状态代码。
  • 3开头 (请求被重定向)表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。
  • 4开头 (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。
  • 5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。
  1. 如何进行网站性能优化

  2. 从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。

  3. 从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。
      总之,恰当的优化不仅能够改善站点的用户体验并且能够节省相当的资源利用。
      前端优化的途径有很多,按粒度大致可以分为两类,第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;第二类则是代码级别的优化,例如 Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。另外,本着提高投入产出比的目的,后文提到的各种优化策略大致按照投入产出比从大到小的顺序排列。
    一、页面级优化

    1. ~JavaScript 压缩和模块打包~
    2. 按需加载资源
    3. 在使用 DOM 操作库时用上 array-ids
    4. 缓存
    5. 启用 HTTP/2
    6. 应用性能分析
    7. 使用负载均衡方案
    8. 为了更快的启动时间考虑一下同构
    9. 使用索引加速数据库查询
    10. 使用更快的转译方案
    11. 避免或最小化 JavaScript 和 CSS 的使用而阻塞渲染
    12. 用于未来的一个建议:使用 service workers + 流
    13. 图片编码优化
  4. 什么是mvvm mvc是什么区别 原理

  • MVC(Model-View-Controller)即模型-视图-控制器。
    Model(模型):
    是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
Model定义了这个模块的数据模型。在代码中体现为数据管理者,Model负责对数据进行获取及存放。
数据不可能凭空生成的,要么是从服务器上面获取到的数据,要么是本地数据库中的数据,也有可能是用户在UI上填写的表单即将上传到服务器上面存放,所以需要有数据来源。既然Model是数据管理者,则自然由它来负责获取数据。
Controller不需要关心Model是如何拿到数据的,只管调用就行了。
数据存放的地方是在Model,而使用数据的地方是在Controller,
所以Model应该提供接口供controller访问其存放的数据(通常通过.h里面的只读属性)

View(视图):
是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。

View,视图,简单来说,就是我们在界面上看见的一切。 它们有一部分是我们UI定死的,也就是不会根据数据来更新显示的, 比如一些Logo图片啊,这里有个按钮啊,那里有个输入框啊,一些显示特定内容的label啊等等; 有一部分是会根据数据来显示内容的,比如tableView来显示好友列表啊, 这个tableView的显示内容肯定是根据数据来显示的。 我们使用MVC解决问题的时候,通常是解决这些根据数据来显示内容的视图。

Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

Controller是MVC中的数据和视图的协调者,也就是在Controller里面把Model的数据赋值给View来显示(或者是View接收用户输入的数据然后由Controller把这些数据传给Model来保存到本地或者上传到
服务器)。

各部分之间的通信方式如下,所有通讯都是单向的 。
[image:6D3DA44A-4FE5-4349-ACB2-54DE7D782737-257-000009D5018FB23C/bg2015020105.png]
1. View 传送指令到 Controller
2. Controller 完成业务逻辑后,要求 Model 改变状态
3. Model 将新的数据发送到 View,用户得到反馈

接受用户指令时,MVC 可以分成两种方式。一种是通过 View 接受指令,传递给 Controller。
[image:F377FCB0-B79B-45D5-A42A-8A10FEBA396C-257-000009EF950C84E9/bg2015020106.png]

另一种是直接通过controller接受指令。
[image:E2283637-42EB-4B1C-8AFC-A0FD5D7EDD95-257-000009F0DAA1A9A8/bg2015020107.png]

  • MVVM(Model-View-ViewModel)模型-视图-视图模型
    【模型】指的是后端传递的数据。
    【视图】指的是所看到的页面。
    【视图模型】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:
    一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
    二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

总结:
在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。
MVVM流程图如下:
[image:5B653B24-958A-482E-BF38-84599A29F265-257-000009FD9BFF8640/bg2015020110.png]

  1. px、em和rem的区别
  • px表示像素 (计算机屏幕上的一个点:1px = 1/96in),是绝对单位,不会因为其他元素的尺寸变化而变化;
  • em表示相对于父元素的字体大小。em是相对单位 ,没有一个固定的度量值,而是由其他元素尺寸来决定的相对值。
  • rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。

任意浏览器的默认字体高都是16px。所以未经调整的浏览器都符合: 1em=16px。那么12px=0.75em, 10px=0.625em。

为了简化计算,在css中的body选择器中声明Font-size=62.5%,这就使em值变为16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。

  1. 优雅降级和渐进增强
  • 渐进增强(Progressive Enhancement):一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。

  • 优雅降级(Graceful Degradation):一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。

  1. eval()的作用
    eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
    语法:
eval(string)
  1. JS哪些操作会造成内存泄露
    JS的回收机制:
    找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。
    垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:“标记清除”和“引用计数”。引用计数不太常用,标记清除较为常用。
    1、标记清除
      这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
      垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
    *关于这一块,建议读读 ,关于作用域链的一些知识详解,读完差不多就知道了,哪些变量会被做标记。
function test(){
  var a=10;//被标记,进入环境
  var b=20;//被标记,进入环境
}
test();//执行完毕之后a、b又被标记离开环境,被回收

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

function test(){
  var a={};//a的引用次数为0
  var b=a;//a的引用次数加1,为1
  var c=a;//a的引用次数加1,为2
  var b={};//a的引用次数减1,为1
}

哪些操作会造成内存泄露:
1.意外的全局变量引起的内存泄露,一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是 window,也就是说:

function foo(arg) {
    bar = "aaaaa";
}

// 实际上等价于
function foo(arg) {
    window.bar = "aaaaa";
}

// 类似的
function foo() {
    this.variable = "qqqqq";
}
//this 指向全局对象(window)
foo();

2.闭包引起的内存泄露


function fn1(){
    var n=1;
    function fn2(){//在加一个fn2当他的子集
        alert(n);
    }
return fn2(); 
//return出来后 他就给 window了所以一直存在内存中。因为一直在内存中,在IE里容易造成内存泄漏
}
fn1();

3.dom清空或删除时,事件未清除导致的内存泄漏

var elements={
    button: document.getElementById("button"),
    image: document.getElementById("image"),
    text: document.getElementById("text")
};
function doStuff(){
    image.src="http://some.url/image";
    button.click():
    console.log(text.innerHTML)
}
function removeButton(){
  document.body.removeChild(document.getElementById('button'))
}

4.循环引用

function leakMemory() {
	var el = document.getElementById('el');
	var o = { 'el': el };
	el.o = o;
}

5.定时器setTimeout和setInterval:当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。比如:vue使用了定时器,需要在beforeDestroy 中做对应销毁处理。js也是一样的。

clearTimeout(***)
clearInterval(***)

6.如果在mounted/created 钩子中使用了 o n , 需 要 在 b e f o r e D e s t r o y 中 做 对 应 解 绑 ( on,需要在beforeDestroy 中做对应解绑( onbeforeDestroy(off)处理

beforeDestroy() {
  this.bus.$off('****');
}

7.死循环

while(1){
	a++;
}

8.给DOM对象添加的属性是一个对象的引用

var testObject = {};
document.getElementById('idname').property = testObject;  //如果DOM不被消除,则testObject会一直存在,造成内存泄漏
  1. bootstrap响应式实现的原理
    百分比布局+媒体查询

  2. CSS样式覆盖规则

  • 规则一:由于继承而发生样式冲突时,最近祖先获胜。

  • 规则二:继承的样式和直接指定的样式冲突时,直接指定的样式获胜

  • 规则三:直接指定的样式发生冲突时,样式权值高者获胜。
    样式的权值取决于样式的选择器,权值定义如下表:

CSS选择器					权值
标签选择器					1
类选择器					10
ID选择器					100
内联样式					1000
伪元素(:first-child)		1
伪类(:link)				10

可以看到,内联样式的权值>>ID选择器>>类选择器>>标签选择器,除此以外,后代选择器的权值为每项权值之和,比如”#nav .current a”的权值为100 + 10 + 1 = 111。

  • 规则四:样式权值相同时,后者获胜。

  • 规则五:!important的样式属性不被覆盖。

  1. position的值, relative和absolute分别是相对于谁进行定位的

    1. absolute:生成绝对定位的元素, 相对于最近一级的定位不是 static 的父元素来进行定位(相对于最近的已经定位,即position为absolute或者relative的元素的祖先元素)。
    2. fixed(老IE不支持)生成绝对定位的元素,通常相对于浏览器窗口或 frame 进行定位。
    3. relative生成相对定位的元素,相对于其在普通流中的位置进行定位(相对于本元素原始位置进行定位)。
    4. static默认值。没有定位,元素出现在正常的流中
    5. sticky生成粘性定位的元素,容器的位置根据正常文档流计算得出
  2. 解释下CSSsprites,以及你要如何在页面或网站中使用它
    CSS Sprites其实就是把网页中一些背景图片整合到一张图片文件中,再利用CSS的“background-image”,“background-repeat”,“background-position”的组合进行背景定位,background-position可以用数字能精确的定位出背景图片的位置

  3. 怎样添加、移除、移动、复制、创建和查找节点?

1)创建新节点

createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点

2)添加、移除、替换、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入

3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
  1. 浏览器的内核分别是什么?
  • IE: trident内核

  • Firefox:gecko内核

  • Safari:webkit内核

  • Opera:以前是presto内核,Opera现已改用Google Chrome的Blink内核

  • Chrome:Blink(基于webkit,Google与Opera Software共同开发)

  1. 请解释JSONP的工作原理,以及它为什么不是真正的AJAX。
    JSONP (JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在 pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。

  2. 请解释一下JavaScript的同源策略。
    在客户端编程语言中,如javascript和 ActionScript,同源策略是一个很重要的安全理念,它在保证数据的安全性方面有着重要的意义。同源策略规定跨域之间的脚本是隔离的,一个域的脚本不能访问和操作另外一个域的绝大部分属性和方法。那么什么叫相同域,什么叫不同的域呢?当两个域具有相同的协议, 相同的端口,相同的host,那么我们就可以认为它们是相同的域。同源策略还应该对一些特殊情况做处理,比如限制file协议下脚本的访问权限。本地的HTML文件在浏览器中是通过file协议打开的,如果脚本能通过file协议访问到硬盘上其它任意文件,就会出现安全隐患,目前IE8还有这样的隐患。

  3. 浏览器是如何渲染页面的?

    1. 解析HTML文件,创建DOM树。自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。

    2. 解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;

    3. 将CSS与DOM合并,构建渲染树(Render Tree)

    4. 布局和绘制,重绘(repaint)和重排(reflow)

  4. 对标签有什么理解

<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
	<meta name="author" content="w3school">
	<meta name="revised" content="David Yang,8/1/07">
	<meta name="generator" content="Dreamweaver 8.0en">
</head>
<body>
	<p>本文档的 meta 属性标识了创作者和编辑软件。</p>
</body>
</html>
  1. 请写出你对闭包的理解,并列出简单的理解
    使用闭包主要是为了设计私有的方法和变量。
    闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
    闭包有三个特性:
    1.函数嵌套函数
    2.函数内部可以引用外部的参数和变量
    3.参数和变量不会被垃圾回收机制回收

  2. JavaScript中如何检测一个变量是一个String类型?请写出函数实现

typeof(obj) === "string"
typeof obj === "string"
obj.constructor === String
  1. 判断一个字符串中出现次数最多的字符,统计这个次数

  2. $(document).ready()方法和window.onload有什么区别?

    1. window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。
    2. $(document).ready()方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。
  3. js遍历

  4. for循环

  5. forEach循环:forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。没有返回值

array.forEach(function(currentValue[, index, arr), thisValue])
3. map()函数:map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
array.map(unction(currentValue,index,arr), thisValue)
4. filter函数:方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
array.filter(function(currentValue[,index,arr), thisValue])
5. some函数:some() 方法用于检测数组中的元素是否满足指定条件(函数提供),some() 方法会依次执行数组的每个元素:
array.some(function(currentValue[,index,arr),thisValue])	
6. 对象in方法
let obj ={a:'2',b:3,c:true};
for (var i in obj) {
    console.log(obj[i],i)
}
console.log(obj);
7. Object.keys(obj)和 Object.values(obj)函数
const obj = {
    id:1,
    name:'zhangsan',
    age:18
}
console.log(Object.keys(obj))
console.log(Object.values(obj))

输出:
[image:A0B17967-71BC-42D6-AFDC-258E90FB11DE-1019-00001254F2A6434A/20190401214628526.png]

  1. js数组处理函数总结
    1. array.push():push() 向数组的末尾添加一个或更多元素,并返回新的长度。
    2. array.pop():删除并返回数组的最后一个元素
    3. array.unshift(): 向数组的开头添加一个或更多元素,并返回新的长度.
    4. array.shift() :删除并返回数组的第一个元素
    5. array.reverse() :方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组
    6. array.sort() :方法用 原地算法 对数组的元素进行排序,并返回数组。排序算法现在是 稳定的 。默认排序顺序是根据字符串Unicode码点。由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。原数组上原地排序,原数组改变
    7. array.concat(array2) :方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组
    8. array.join():creates and returns a new string by concatenating all of the elements in an array (or an array-like object ), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"
arr.join()
//	 output: George,John,Thomas
10. string.slice(start,end) :方法提取一个字符串的一部分,返回一个从原字符串中提取出来的新字符串。
// 语法:
str.slice(beginSlice[, endSlice])

beginSlice:
必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。

endSlice:
可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

//实例: 
var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"
arr.slice(1) // output: John,Thomas

  1. array.splice() :方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
// 语法:
array.splice(startIndex,howmany[,item1,.....])

// 示例1: 添加元素
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2,1,"Lemon","Kiwi");	// output: Banana,Orange,Lemon,Kiwi,Mango

// 示例2: 删除元素
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2,2);	// output: Banana,Orange
13. array.indexOf():返回数组对象的原始值
14. array.reduce(reducer):方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为**单个返回值**。
     reducer函数接收4个参数:
1	Accumulator (acc) (累计器)
2	Current Value (cur) (当前值)
3	Current Index (idx) (当前索引)
4	Source Array (src) (源数组)

15. array.map():对数组的每一项应用回调函数,返回**新数组**。
16. array.some():数组中**只需有一项**满足给定条件则返回true。
17. array.filter():方法创建一个**新数组,** 其包含通过所提供函数实现的测试的所有元素。
18. array.every():数组的**每一项**都满足给定条件则返回true。
19. forEach:数组遍历,与for循环一样,**没有返回**。 
  1. let与var的区别?
    Let为ES6新添加申明变量的命令,它类似于var,但是有以下不同:
    1、var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
    2、let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
    3、let不允许重复声明.

  2. 封装一个函数,参数是定时器的时间,.then执行回调函数。

function sleep (time) { 
return new Promise((resolve) => setTimeout(resolve, time));
}
  1. 项目做过哪些性能优化?
    1、减少HTTP请求数
    2、减少DNS查询
    3、使用CDN
    4、避免重定向
    5、图片懒加载
    6、减少DOM元素数量
    7、减少DOM操作
    8、使用外部JavaScript和CSS
    9、压缩JavaScript、CSS、字体、图片等
    10、优化CSS Sprite
    11、使用iconfont
    12、字体裁剪
    13、多域名分发划分内容到不同域名
    14、尽量减少iframe使用
    15、避免图片src为空
    16、把样式表放在 中
    17、把脚本放在页面底部

  2. 怎么判断两个对象相等?
    1、Object.is(obj1,obj2):判断两个值是否 相同 。如果下列任何一项成立,则两个值相同。

  • 两个值都是 undefined
  • 两个值都是 null
  • 两个值都是true或者都是false
  • 两个值是由相同个数的字符按照相同的顺序组成的字符串
  • 两个值指向同一个对象
  • 两个值都是数字并且
    • 都是正零+0
    • 都是负零-0
    • 都是 NaN
    • 都是除零和 NaN 外的其它同一个数字

2、转化成字符串后比较字符串是否一致:

JSON.stringify(obj)===JSON.stringify(obj2);
  1. 什么是模块化开发?
    实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据:
    [image:337955B4-2A59-403E-A11B-7B2535221F33-250-000005EE1CA615A5/6187ce83f3cf77f8ec107289ccd28b31.jpg]

关键点在于data如何更新view,因为view更新data其实可以通过事件监听即可,比如input标签监听 ‘input’ 事件就可以实现了。所以我们着重来分析下,当数据改变,如何更新视图的。
数据更新视图的重点是如何知道数据变了,只要知道数据变了,那么接下去的事都好处理。如何知道数据变了,其实上文我们已经给出答案了,就是通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。

Vue是通过Object.defineProperty()来实现数据劫持的。它可以来控制一个对象属性的一些特有操作,比如set()、get()、是否可以枚举。
[image:FC190A7C-788F-47F6-8486-9BAE1C1160B6-250-0000058EA404BA34/20a8fe96eeee.jpg]
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

流程图如下:
[image:754F1E6A-222C-4D46-B6D7-3C2AC9C5E25D-250-000005DA403642FA/95f73c0a2f86.jpg]

  1. 浏览器兼容性问题
    [image:15ABDE16-22D0-49F2-BB91-5DF935054D39-853-000007D4AC4AD2E9/屏幕快照 2019-05-10 22.22.58.png]
    1、Normalize.css
    2、不同浏览器的标签默认的外边距和内边距不同:
* {
padding: 0;	
margin: 0;
}

3、IE6双边距问题:在 IE6中设置了float , 同时又设置margin , 就会出现边距问题:设置display:inline;
4、图片默认有间距:使用float 为img 布局
5、IE9以下浏览器不能使用opacity:

opacity: 0.5;
filter: alpha(opacity = 50);
filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 50);

6、cursor:hand 显示手型在safari 上不支持:统一使用 cursor:pointer
7、当标签的高度设置小于10px,在IE6、IE7中会超出自己设置的高度:超出高度的标签设置overflow:hidden,或者设置line-height的值小于你的设置高度
8、CSS HACK的方法:

height: 100px;	// 所有浏览器 通用 
_height: 100px;	// IE6 专用 
*+height: 100px;	// IE7 专用 
*height: 100px;	// IE6、IE7 共用 
height: 100px !important;		// IE7、FF 共用 

代码的顺序一定不能颠倒了,要不又前功尽弃了。因为浏览器在解释程序的时候,如果重名的话,会用后面的覆盖前面的,就象给变量赋值一个道理,所以我们把通用的放前面,越专用的越放后面

  1. 前端跨域
    1. 什么是跨域?

只要协议、域名、端口有任何一个不同,都被当作是不同的域。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

注意点:
如果是协议和端口造成的跨域问题“前台”是无能为力的;
在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。 (“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。)

2. 什么是同源策略?

所谓同源是指,域名,协议,端口相同。浏览器采用同源策略,就是禁止页面加载或执行与自身来源不同的域的任何脚本。

3. 通过document.domain跨域(只适用于不同子域的框架间的交互)
浏览器有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档。第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是 [www.damonare/a.html], 在这个页面里面有一个iframe,它的src是 [damonare/b.html], 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:
<script type="text/javascript">
    function test(){
        var iframe = document.getElementById('ifame');
        var win = iframe.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
        var doc = win.document;//这里获取不到iframe里的document对象
        var name = win.name;//这里同样获取不到window对象的name属性
    }
</script>
<iframe id = "iframe" src="http://damonare/b.html" onload = "test()"></iframe>
这个时候,document.domain就可以派上用场了,我们只要把 [www.damonare/a.html] 和  [damonare/b.html] 这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

在页面 [www.damonare/a.html] 中设置document.domain:

<iframe id = "iframe" src="http://damonare/b.html" onload = "test()"></iframe> <script type="text/javascript"> document.domain = 'damonare';//设置成主域 
function test(){ alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象 } </script>

在页面 damonare/b.html 中也设置document.domain:

<script type="text/javascript">
    document.domain = 'damonare';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>
4. 通过location.hash跨域

因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他 window的location来发送消息(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe),并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,这样做也存在缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等。下面举例说明:

假如父页面是baidu/a.html,iframe嵌入的页面为google/b.html(此处省略了域名等url属性),要实现此两个页面间的通信可以通过以下方法。
  • a.html传送数据到b.html
    • a.html下修改iframe的src为google/b.html#paco
    • b.html监听到url发生变化,触发相应操作
  • b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe
    • b.html下创建一个隐藏的iframe,此iframe的src是baidu域下的,并挂上要传送的hash数据,如src=" www.baidu/proxy.html#… "
    • proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
    • a.html监听到url发生变化,触发相应操作
      B.html页面的关键代码如下:
try {  
    parent.location.hash = 'data';  
} catch (e) {  
    // ie、chrome的安全机制无法修改parent.location.hash,  
    var ifrproxy = document.createElement('iframe');  
    ifrproxy.style.display = 'none';  
    ifrproxy.src = "http://www.baidu/proxy.html#data";  
    document.body.appendChild(ifrproxy);  
}

Proxy.html页面的关键代码如下 :

//因为parent.parent(即baidu/a.html)和baidu/proxy.html属于同一个域,所以可以改变其location.hash的值  
parent.parent.location.hash = self.location.hash.substring(1);
5. 通过HTML5的postMessage方法跨域

高级浏览器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都将支持这个功能。这个功能主要包括接受信息的"message"事件和发送消息的"postMessage"方法。比如damonare域的A页面通过iframe嵌入了一个google域的B页面,可以通过以下方法实现A和B的通信

A页面通过postMessage方法发送消息:

window.onload = function() {  
    var ifr = document.getElementById('ifr');  
    var targetOrigin = "http://www.google";  
    ifr.contentWindow.postMessage('hello world!', targetOrigin);  
};

postMessage的使用方法:
otherWindow.postMessage(message, targetOrigin);
* otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
* message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
* targetOrigin: 是限定消息接收范围,不限制请使用 '*

B页面通过message事件监听并接受消息:

var onmessage = function (event) {  
  var data = event.data;//消息  
  var origin = event.origin;//消息来源地址  
  var source = event.source;//源Window对象  
  if(origin=="http://www.baidu"){  
console.log(data);//hello world!  
  }  
};  
if (typeof window.addEventListener != 'undefined') {  
  window.addEventListener('message', onmessage, false);  
} else if (typeof window.attachEvent != 'undefined') {  
  //for ie  
  window.attachEvent('onmessage', onmessage);  
}

同理,也可以B页面发送消息,然后A页面监听并接受消息。

6. 通过jsonp跨域

刚才说的这几种都是双向通信的,即两个iframe,页面与iframe或是页面与页面之间的,下面说几种单项跨域的(一般用来获取数据),因为通过script标签引入的js是不受同源策略的限制的。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。

比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是 damonare/data.php , 那么a.html中的代码就可以这样:

<script type="text/javascript">
    function dosomething(jsondata){
        //处理获得的json数据
    }
</script>
<script src="http://example/data.php?callback=dosomething"></script>

我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。
因为是当做一个js文件来引入的,所以 damonare/data.php 返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的(一定要和后端约定好哦):

<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

最终,输出结果为:dosomething([‘a’,‘b’,‘c’]);
如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

<script type="text/javascript">
    $.getJSON('http://example/data.php?callback=?,function(jsondata)'){
        //处理获得的json数据
    });
</script>

Jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

JSONP的优缺点
* JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
* JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

7. 通过CORS跨域

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
平时的ajax请求可能是这样的:

<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open("POST", "/damonare",true); xhr.send(); </script>

作者:Damonare
链接:https://juejin.im/post/5815f4abbf22ec006893b431
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

以上damonare部分是相对路径,如果我们要使用CORS,相关Ajax代码可能如下所示:

<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open("GET", "http://segmentfault/u/trigkit4/",true); xhr.send(); 
</script>

代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。关于CORS更多了解可以看下阮一峰老师的这一篇文章: 跨域资源共享 CORS 详解

CORS和JSONP对比
* JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
* 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
* JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。

CORS与JSONP相比,无疑更为先进、方便和可靠。

7. 通过window.name跨域

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

比如:我们在任意一个页面输入

window.name = "My window's name";
setTimeout(**function**(){
    window.location.href = "http://damonare/";
},1000)
复制代码

进入damonare页面后我们再检测再检测 window.name :

window.name; // My window’s name

可以看到,如果在一个标签里面跳转网页的话,我们的 window.name 是不会改变的。
基于这个思想,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。在这个页面中就可以获取到我们刚刚设置的 window.name 了。

由于安全原因,浏览器始终会保持 window.name 是string 类型。
同样这个方法也可以应用到和iframe的交互来:
比如:我的页面( [damonare/index.html]

<iframe id="iframe" src="http://www.google/iframe.html"></iframe>

在 iframe.html 中设置好了 window.name 为我们要传递的字符串。
我们在 index.html 中写了下面的代码:

var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { data = iframe.contentWindow.name; };

报错!因为两个页面不同源嘛,想要解决这个问题可以这样干:

var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { iframe.onload = function(){ data = iframe.contentWindow.name; } iframe.src = 'about:blank'; };

或者将里面的 about:blank 替换成某个同源页面(about:blank,javascript: 和 data: 中的内容,继承了载入他们的页面的源。)

这种方法与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。

其它诸如中间件跨域,服务器代理跨域,Flash URLLoader跨域,动态创建script标签(简化版本的jsonp)不作讨论。


  1. 前端优化:浏览器缓存
    前端优化:浏览器缓存技术介绍 - 掘金
    浏览器缓存分为强缓存和协商缓存。

当客户端请求某个资源时,获取缓存的流程如下:
1)浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如:某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;

2)当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;

3)强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。

4)当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。

  1. jsonp的原理与实现
    Jsonp是一种跨域通信的手段,它的原理其实很简单:
    1. 首先是利用script标签的src属性来实现跨域。

    2. 通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。

    3. 由于使用script标签的src属性,因此只支持get方法

实现流程:
1、设定一个script标签

<script src="http://jsonp.js?callback=xxx"></script>

2、callback定义了一个函数名,而远程服务端通过调用指定的函数并传入参数来实现传递参数,将function(response)传递回客户端

$callback = !empty($_GET['callback']) ? $_GET['callback'] : 'callback';
echo $callback.'(.json_encode($data).)';

3、客户端接收到返回的js脚本,开始解析和执行function(response)

  • 简单的实例:
    一个简单的jsonp实现,其实就是拼接url,然后将动态添加一个script元素到头部。
function jsonp(req){
    var script = document.createElement('script');
    var url = req.url + '?callback=' + req.callback.name;
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script); 
}

前端js示例:

function hello(res){
    alert('hello ' + res.data);
}
jsonp({
    url : '',
    callback : hello 
});

服务器端代码:

var http = require('http');
var urllib = require('url');

var port = 8080;
var data = {'data':'world'};

http.createServer(function(req,res){
    var params = urllib.parse(req.url,true);
    if(params.query.callback){
        console.log(params.query.callback);
        //jsonp
        var str = params.query.callback + '(' + JSON.stringify(data) + ')';
        res.end(str);
    } else {
        res.end();
    }
    
}).listen(port,function(){
    console.log('jsonp server is on');
});
  • 可靠的jsonp实例:
(function (global) {
    var id = 0,
        container = document.getElementsByTagName("head")[0];

    function jsonp(options) {
        if(!options || !options.url) return;

        var scriptNode = document.createElement("script"),
            data = options.data || {},
            url = options.url,
            callback = options.callback,
            fnName = "jsonp" + id++;

        // 添加回调函数
        data["callback"] = fnName;

        // 拼接url
        var params = [];
        for (var key in data) {
            params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
        }
        url = url.indexOf("?") > 0 ? (url + "&") : (url + "?");
        url += params.join("&");
        scriptNode.src = url;

        // 传递的是一个匿名的回调函数,要执行的话,暴露为一个全局方法
        global[fnName] = function (ret) {
            callback && callback(ret);
            container.removeChild(scriptNode);
            delete global[fnName];
        }

        // 出错处理
        scriptNode.onerror = function () {
            callback && callback({error:"error"});
            container.removeChild(scriptNode);
            global[fnName] && delete global[fnName];
        }

        scriptNode.type = "text/javascript";
        container.appendChild(scriptNode)
    }

    global.jsonp = jsonp;

})(this);

使用示例:

jsonp({
    url : "www.example",
    data : {id : 1},
    callback : function (ret) {
        console.log(ret);
    }
});

本文标签: 端面 试题 年前