知识点积累,有转载。
- js函数调用时加括号和不加括号的区别:
- 不加括号相当于把函数代码赋给等号左边
- 加括号是把函数返回值赋给等号左边
- this:函数执行上下文。可以通过call / apply / bind改变this。
对于匿名函数或者直接调用的函数来说,this 指向全局上下文(浏览器为window,nodejs为global),剩下的函数调用,就是谁调用它,this就指向谁。当然含有ES6的箭头函数,箭头函数的指向取决于该尖头函数声明的位置,即在哪里声明,this就指向哪里。
- 以函数的形式调用时,this永远都是window。 test() this-> window
- 以方法的形式调用时,this就是调用方法的对象。 p.test() this-> p
- 以构造函数的形式调用时,this就是新创建的对象。 new test() this-> 新创建的对象
- 使用call和apply调用时,this就是指定的那个对象。 p.call(obj) this-> obj
- 在全局作用域中this代表window
- JavaScript实现继承共6种方式:
- 原型链继承
- 借用构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
const定义变量不可以被重写,定义对象可以修改属性
const 声明创建一个值的只读引用(即指针),基本数据当值发生改变时,那么其对应的指针也会发生变化,故造成const声明基本数据类型后再将其值改变,会报错;但是如果是复合类型,如果只改变复合类型中的某个项的value时,将还可以正常使用。
const a = 2;
a = 3;
console.log(a) // TypeError报错
const a = {b: 3};
console.log(a.b); // 3
a.b = 4;
console.log(a.b) // 4
-
Javascript内置可迭代对象
String,Array,TypedArray,Map,Set是所有内置可迭代对象,因为他们的原型对象都有一个@@iterator方法 -
javascript的全局属性 和 全局函数
- 全局属性
- Infinity:代表正的无穷大的数值。
- NaN:指示某个值是不是数字值。
- undefined:指示未定义的值。
- 全局函数:6(编码相关)+ 2(数据处理)+ 4(数字相关)+ 1(特殊)
- 编码相关:
- escape():对字符串进行编码。
- unescape():对由 escape() 编码的字符串进行解码。
- encodeURI():把字符串编码为 URI。
- encodeURIComponent():把字符串编码为 URI 组件。
- decodeURI():解码某个编码的 URI。
- decodeURIComponent():解码一个编码的 URI 组件。
- 数据处理
- Number():把对象的值转换为数字。
- String():把对象的值转换为字符串。
- 数字相关
- isFinite():检查某个值是否为有穷大的数。
- isNaN():检查某个值是否是数字。
- parseFloat():解析一个字符串并返回一个浮点数。
- parseInt():解析一个字符串并返回一个整数。
- 特殊
- eval():计算 JavaScript 字符串,并把它作为脚本代码来执行。
- 编码相关:
- var datas=[10,20,30];
- datas.unshift(40,50); // 首部添加 [40,50,10,20,30]
- data.pop(); // 尾部删除 [40,50,10,20]
- datas.push(60,70); // 尾部添加 [40,50,10,20,60,70]
- data.shift(); // 首部删除 [50,10,20,60,70]
- JavaScript是单线程的,浏览器实现了异步的操作,整个js程序是事件驱动的,每个事件都会绑定相应的回调函数。在标准的 JavaScript 中, Ajax 异步执行调用基于:event和callback。
- 如何规避javascript多人开发函数重名问题?
- 可以通过命名规范,比如根据不同的开发人员实现的功能,在函数名加前缀
- 每个开发人员都把自己的函数封装到类中,然后调用的时候即使函数名相同,但是因为是要 类.函数名 来调用,所以也减少了重复的可能性
- 原生 js 操作数组的方法
-
concat:会创建新数组,将多个数组相连。不会改变原有数组。
-
slice(start,end):会创建新数组 接收起始和终点位置参数,截取部分元素。不会改变原有数组。
-
splice(start, deleteCount, item1,…itemX):不会创建新数组,直接在原数组中修改。(item1,…itemX:可选,向数组添加的新项目)。会改变原有数组。
-
push / pop:数组后方增加/删除。会改变原有数组。
-
unshift / shift:数组前方增加/删除。会改变原有数组。
-
reverse() : 反转元素的顺序。会改变原有数组。
-
sort(): 按升序排列。会改变原有数组。
[3, 2, 1].sort(function (a, b) { return a - b; });
说明:使用sort进行排序,排序规则按照写的numberSort函数。 numberSort函数中,返回的值是负数,即a-b<0,那么位置不变,仍是a在前b在后。若a-b>0,则需要交换位置,交换之后:a在后b在前。a - b = 0,a 和 b 的相对位置不变。
升序排序return a-b,降序排序return b-a
- 会改变数组的方法:
- push() / pop() / shift() / unshift()
- splice()
- sort()
- reverse()
- forEach()
- 不会改变数组的方法:
- filter()
- concat()
- slice()
- map()
- 闭包
-
闭包就是能够读取其他函数内部变量的函数。
-
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包最常见的方式之一就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。利用闭包可以突破作用域链。
-
闭包的特性
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
-
说说你对闭包的理解
- 使用闭包主要是为了设计私有的方法和变量。
- 闭包的优点是可以避免全局变量的污染;缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄漏;
- 在JS中,函数即闭包,只有函数才会产生作用域的概念。
- 闭包的最大用处有两个:
- 一是可以读取函数内部的变量
- 二是让这些变量始终保持在内存中
- 闭包的另一个用处是封装对象的私有方法和私有属性
-
使用闭包的注意点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题,如内存泄漏
- 解决方法是:在推出函数之前,将不使用的局部变量全部删除
闭包相关
- 闭包就是内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
function add(x) { var sum = x; var tmp = function (y) { sum = sum + y; return tmp; }; tmp.toString = function () { return sum; }; return tmp; } console.log(add(1)(2)(3)); //6
解析:
- 代码执行add(1)的时候,声明了add函数的局部变量sum并赋值为1,同时返回子函数tmp,这样add(1)(2)(3)就相当于tmp(2)(3),因为tmp函数需要用的sum这个变量,使得add执行完毕之后并没有清除sum这个局部变量的数据。这样执行tmp(2)的时候将2与sum相加保存在sum上,同时返回自身tmp。这时sum为3,tmp(2)(3)就相当于tmp(3),然后运行tmp(3),把3与sum相加保存在变量sum上,同时返回tmp。这时add(1)(2)(3)运行之后结果是tmp函数(sum=6),而用console.log()函数显示结果的时候会将里面的内容自动转换为字符串,所以console.log(tmp)相当于console.log(tmp.toString()),而这个toString()函数被重定义为return sum,所以结果就是console.log(sum)//6,以此类推如果后面还有括号那么sum将继续加下去到最后剩下tmp然后运行toString()返回结果。
- add(1)的时候,tmp=function()根本就没有运行,函数里面又没有被调用,自然不会运行了。所以就不存在sum=sum+x的运行情况。只有等到tmp作为返回值之后,才被外部的调用,才开始所谓的tmp=function(x)的运行,才会有sum+x 的运行发生。
- 简化版:
第一次调用的是add()方法,这时sum=1;因为add()最后return temp,所以第二次调用的是temp()方法,2就传到temp那里去了,这时sum=sum+2;因为temp()也是return temp;所以第三次sum=sum+3
- 说说你对作用域的理解
- 作用域:就是一块地盘,一个代码段所在的区域。它是静态的(相当于上下文对象),在编写代码时就确定了。
- 作用域分类:
- 全局作用域
- 局部作用域
- 没有块作用域(ES6有了)
- 作用域的作用
隔离变量,不同作用域下同名变量不会冲突 - 多个上下级关系的作用域形成的作用域链,它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
-
JS原型,原型链,constructor? 有什么特点?
1. prototype
- 每个函数都有一个 prototype 属性。
- 每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
2. proto
这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
实例对象的隐式原型属性__proto__ = 构造函数的显式原型属性prototype
3. constructor
每个原型都有一个constructor属性,指向该关联的构造函数。
function Person() { } var person = new Person(); console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true // 顺便学习一个ES5的方法,可以获得对象的原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true
console.log(person.constructor === Person); // true
👆当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
person.constructor === Person.prototype.constructor
4. 实例与原型
function Person() { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin
- 给实例对象 person 添加了 name 属性,当打印 person.name 的时候,结果自然为 Daisy。
- 但是当删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是找到了 name 属性,结果为 Kevin。
- 但是万一还没有找到呢?原型的原型又是什么呢?
5. 原型的原型
在前面,已经讲了原型也是一个对象,既然是对象,就可以用最原始的方式创建它,那就是:
var obj = new Object(); obj.name = 'Kevin' console.log(obj.name) // Kevin
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:
6. 原型链
回顾一下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。
——摘自《javascript高级程序设计》其实就是上面4-5的过程。
继上述5中所说,那 Object.prototype 的原型呢?
console.log(Object.prototype.__proto__ === null) // true
所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找。如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
-
关于原型链的题
-
1
Function.prototype.a = 'a'; Object.prototype.b = 'b'; function Person(){}; var p = new Person(); console.log('p.a: '+ p.a); // p.a: undefined console.log('p.b: '+ p.b); // p.b: b
解析:有不少同学第一眼看上去就觉得很疑惑,p不是应该继承了Function原型里面的属性吗,为什么p.a返回值是undefined呢?
其实,只要仔细想一想就很容易明白了,Person函数才是Function对象的一个实例,所以通过Person.a可以访问到Function原型里面的属性,但是**new Person()返回来的是一个对象,它是Object的一个实例,是没有继承Function的,所以无法访问Function原型里面的属性。**但是,由于在js里面所有对象都是Object的实例,所以,Person函数可以访问到Object原型里面的属性,Person.b => ‘b’ -
2
var F=function(){}; Object.prototype.a=function(){}; Function.prototype.b=function(){}; var f=new F();
这里一样,F函数是才是function对象的一个实例所以通过F.b可以访问到Function原型里面的属性。但是new F()返回的是一个对象,是Object的一个实例,是没有继承Function的,所以拿不到b;但是由于在js中所有对象都是Object的实例,所以可以拿到a。
- Javascript如何实现继承
-
构造继承
-
原型继承
function Parent(){ this.name = 'wang'; } function Child(){ this.age = 28; } Child.prototype = new Parent();//继承了Parent,通过原型 var demo = new Child(); alert(demo.age); alert(demo.name);//得到被继承的属性
-
实例继承
-
拷贝继承
- 事件的各个阶段
捕获阶段 —> 目标阶段 —> 冒泡阶段
document —> target目标 ----> document
- element.addEventListener(event, function, useCapture)
- event:指定事件名
- function:指定事件触发时执行的函数
- useCapture:指定事件是否在捕获或冒泡阶段执行
true
- 事件句柄在捕获阶段
执行(由外往内传递时)false
- 默认。事件句柄在冒泡阶段
执行(由内向外传递时)
<div id="outA">
<div id="outB">
<div id="outC"></div>
</div>
</div>
像上面的结构,假如outA、outB、outC都注册了click类型事件处理函数,当点击outC的时候,触发顺序是A–>B–>C,还是C–>B–>A呢?这就分两种情况了:
- 如果浏览器采用的是事件冒泡,那么触发顺序是C–>B–>A,由内而外
<script>
window.onload = function(){
var outA = document.getElementById('outA');
var outB = document.getElementById('outB');
var outC = document.getElementById('outC');
// 使用事件冒泡
outA.addEventListener('click', function(){alert(1);}, false);
outB.addEventListener('click', function(){alert(2);}, false);
outC.addEventListener('click', function(){alert(3);}, false);
}
</script>
<div id="outA">
<div id="outB">
<div id="outC"></div>
</div>
</div>
- 如果浏览器采用的是事件捕获,那么触发顺序是A–>B–>C,从外到内
- DOM事件流
DOM事件流包含三个阶段:事件捕获 -> 目标事件 -> 事件冒泡
下面的代码,当点击outC的时候,依次打印出 capture1–>capture2–>target–>bubble2–>bubble1 👇👇
<script>
window.onload = function(){
var outA = document.getElementById('outA');
var outB = document.getElementById('outB');
var outC = document.getElementById('outC');
// 目标(自身触发事件,是捕获还是冒泡无所谓)
outC.addEventListener('click', function(){alert('target');}, true);
// 事件冒泡
outA.addEventListener('click', function(){alert('bubble1');}, false);
outB.addEventListener('click', function(){alert('bubble2');}, false);
// 事件捕获
outA.addEventListener('click', function(){alert('capture1');}, true);
outB.addEventListener('click', function(){alert('capture2');}, true);
}
</script>
<div id="outA">
<div id="outB">
<div id="outC"></div>
</div>
</div>
看一下事件函数的执行顺序:
// 目标(自身触发事件,是捕获还是冒泡无所谓)
outC.addEventListener('click', function(){alert('target');}, true);
在DOM事件流中提到了上面的这个。那如果在outC上面同时绑定了捕获事件和冒泡事件的话会怎样?👇👇
<script>
window.onload = function(){
var outA = document.getElementById('outA');
var outB = document.getElementById('outB');
var outC = document.getElementById('outC');
// 目标(自身触发事件,是捕获还是冒泡无所谓)
outC.addEventListener('click',function(){alert("target1");},false);
outC.addEventListener('click',function(){alert("target2");},true);
outC.addEventListener('click',function(){alert("target3");},true);
outC.addEventListener('click',function(){alert("target4");},false);
// 事件冒泡
outA.addEventListener('click', function(){alert('bubble1');}, false);
outB.addEventListener('click', function(){alert('bubble2');}, false);
// 事件捕获
outA.addEventListener('click', function(){alert('capture1');}, true);
outB.addEventListener('click', function(){alert('capture2');}, true);
}
</script>
<div id="outA">
<div id="outB">
<div id="outC"></div>
</div>
</div>
点击outC的时候,打印顺序是:capture1–>capture2–target2-->target3-->target1-->target4
–>bubble2–>bubble1
这个结果能说明:捕获阶段的处理函数最先执行,其次是目标阶段的处理函数,最后是冒泡阶段的处理函数。目标阶段的处理函数(捕获->冒泡),先注册的先执行,后注册的后执行。
- 阻止事件捕获和冒泡
默认情况下,多个事件处理函数会按照DOM事件模型中的顺序执行。但如果子元素上面发生某个事件,不需要执行父元素上注册的处理函数,这时就可以阻止事件捕获和冒泡,避免没有意义的函数调用。
- event.stopPropagation()阻止事件的继续传播
window.onload = function () {
var outA = document.getElementById('outA');
var outB = document.getElementById('outB');
var outC = document.getElementById('outC');
// 目标
outC.addEventListener('click', function (event) {
alert('target');
// 这里阻止
event.stopPropagation();
}, false);
// 事件冒泡
outA.addEventListener('click', function () { alert('bubble'); }, false);
// 事件捕获
outA.addEventListener('click', function () { alert('capture'); }, true);
}
</script>
<div id="outA">
<div id="outB">
<div id="outC"></div>
</div>
</div>
👆上面输出的是 capture -> target,不会打印bubble,因为当事件传播到outC上的处理函数时,通过stopPropagation阻止了事件的继续传播,所以不会传播到冒泡阶段。
👇下面输出的是 capture,不会再往后执行。因为将event.stopPropagation();添加到outA上中。
window.onload = function () {
var outA = document.getElementById("outA");
var outB = document.getElementById("outB");
var outC = document.getElementById("outC");
// 目标
outC.addEventListener('click', function (event) { alert("target"); }, false);
// 事件冒泡
outA.addEventListener('click', function () { alert("bubble"); }, false);
// 事件捕获
outA.addEventListener('click', function () {
alert("capture");
event.stopPropagation();
}, true);
}
- 事件的代理 / 委托
事件代理/ 委托就是指将一个元素响应事件(click / keydown / …)的函数委托到另一个元素。一般来讲,会把一个或者一组元素的事件委托到它的父级或者更外层元素上,真正绑定的事件是更外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件,然后在外层元素上执行函数。
- 优点
- 可以减少事件注册,节省大量内存占用
- 可以将事件应用于动态添加的子元素上
- 缺点:使用不当会造成事件在不该触发时触发
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
- new操作符具体干了什么
function Object(){
}
let obj = new Object();
- 创建一个空对象
- 链接到原型:把 obj 的proto 指向构造函数Object的原型对象 prototype
- 绑定this值(让Object中的this指向obj,并执行Object的函数体。)
- 返回新对象(判断Object的返回值类型:如果无返回值 或者 返回一个非对象值,则将 obj 作为新对象返回;否则会将 result 作为新对象返回。)
- Ajax原理
- 简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据。
- Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。XMLHttpRequest是ajax的核心机制
/** 1. 创建连接 **/
var xhr = null;
xhr = new XMLHttpRequest()
/** 2. 连接服务器 **/
xhr.open('get', url, true)
/** 3. 发送请求 **/
xhr.send(null);
/** 4. 接受请求 **/
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else {
/** false **/
fail && fail(xhr.status);
}
}
}
- ajax优点
- 通过异步模式,提升了用户体验.
- 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用
- Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载
- Ajax可以实现局部刷新
- ajax缺点
- AJAX暴露了与服务器交互的细节
- 对搜索引擎的支持比较弱
- 不容易调试
-
同源策略
协议+域名+端口 -
解决跨域问题
- 通过jsonp跨域:利用< script> 标签没有跨域限制的漏洞,当需要通讯时,通过< script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据。
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script> <script> function jsonp(data) { console.log(data) } </script>
- document.domain + iframe跨域(此方案仅限主域相同,子域不同的跨域应用场景)
- nginx代理跨域
- nodejs中间件代理跨域
- 后端在头部信息里面设置安全域名
-
模块化开发
使用立即执行函数,不暴露私有成员
- 模块就是一个有特定功能的文件,我们可以通过加载这些模块得到特定的功能
- 模块化开发就是js的功能分离,通过需求引入不同的文件
- 模块化开发可以使代码耦合度降低,避免代码多次在页面出现,他最大的作用就是重用
- 组件化开发与模块化开发
- 组件化开发:最初的目的是代码重用,功能相对单一或者独立。在整个系统的代码层次上位于最底层,被其他代码所依赖,所以说组件化是纵向分层。组件化就比如公共的alert框,最初在许多页面都有使用,后面提取出一份相同的代码,其实就是基于代码复用的目的。
- 模块化开发:最初的目的是将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。不同模块之间也会存在依赖关系,但大部分都是业务性的互相跳转,从地位上来说它们都是平级的。模块化就比如一个资讯功能,它本身只在这一个地方使用,没有复用的需求,但系统启动的时候要初始化它的数据,首页显示的时候要展示它的数据,显示红点的时候要拉取它的未读数。这样一来应用中就有很多地方涉及到它的代码。如果我们将它看做一个整体,那么资讯模块和主应用的耦合性就非常高了。所以我们也要把它封装成模块,把相关的代码放到独立的单元文件里,并提供公共方法,这就是高内聚的要求。
- 异步加载JS的方式有哪些?
- 设置< script>属性 async=“async” (一旦脚本可用,则会异步执行)
- 动态创建 script DOM:document.createElement(‘script’);
- XmlHttpRequest 脚本注入
- defer:只支持IE。如果您的脚本不会改变文档的内容,可将 defer 属性加入到< script>标签中,以便加快处理文档的速度
- 哪些操作会造成内存泄漏
JavaScript 内存泄露指对象在不需要使用它时仍然存在,导致占用的内存不能使用或回收
- 未使用 var 声明的全局变量
- 闭包函数(Closures)
- 循环引用(两个对象相互引用)
- 控制台日志(console.log)
- 移除存在绑定事件的DOM元素(IE)
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
- XML和JSON的区别
- 数据体积方面:JSON相对于XML来讲,数据的体积小,传递的速度更快些
- 数据交互方面:JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
- 数据描述方面:JSON对数据的描述性比XML较差
- 传输速度方面:JSON的速度要远远快于XML
- 谈谈你对webpack的看法
- WebPack 是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。
- webpack能够很好地管理、打包Web开发中所用到的HTML、Javascript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。
- 对于不同类型的资源,webpack有对应的模块加载器。
- webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。
- 说说你对AMD和Commonjs的理解
- CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
- AMD规范则是非同步加载模块,允许指定回调函数。
- CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的。
- AMD推荐的风格通过返回一个对象做为模块对象。
- CommonJS 的规范中,每个 JavaScript 文件就是一个独立的模块上下文(module context),在这个上下文中默认创建的属性都是私有的。也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的
- 用过哪些设计模式?
- 工厂模式:我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,用工厂方法代替new操作的一种模式。解决了重复实例化的问题
-
为什么要有同源限制?
同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了
- offsetWidth/offsetHeight:
返回值包含content + padding + border,效果与e.getBoundingClientRect()相同 - clientWidth/clientHeight:
返回值只包含content + padding,如果有滚动条,也不包含滚动条 - scrollWidth/scrollHeight:
返回值包含content + padding + 溢出内容的尺寸
- javascript有哪些方法定义对象
- 对象字面量: var obj = {};
- 构造函数: var obj = new Object();
- Object.create(): var obj = Object.create(Object.prototype);
- 常见兼容性问题
-
png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8
-
浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。但是全局的效率很低,一般是如下这样解决:
body,ul,li,ol,dl,dt,dd,form,input,h1,h2,h3,h4,h5,h6,p{ margin:0; padding:0; }
- 你觉得jQuery源码有哪些写的好的地方
- jquery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入window对象参数,可以使window对象作为局部变量使用,好处是当jquery中访问window对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window对象。同样,传入undefined参数,可以缩短查找undefined时的作用域链
- jquery将一些原型属性和方法封装在了jquery.prototype中,为了缩短名称,又赋值给了jquery.fn,这是很形象的写法
- jquery实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率
- vue、react、angular
- Vue.js 一个用于创建 web 交互界面的库,是一个精简的 MVVM。它通过双向数据绑定把 View 层和 Model 层连接了起来。实际的 DOM 封装和输出格式都被抽象为了Directives 和 Filters。
- AngularJS 是一个比较完善的前端MVVM框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注入等所有功能,模板功能强大丰富,自带了丰富的 Angular指令。
- React 仅仅是 VIEW 层,是facebook公司推出的一个用于构建UI的一个库,能够实现服务器端的渲染。用了virtual dom,所以性能很好。
- Web开发中会话跟踪的方法有哪些
客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。在一个会话的多个请求中共享数据,这就是会话跟踪。
- cookie:Cookie是Web服务器发送给客户端的一小段信息,客户端请求时可以读取该信息发送到服务器端,进而进行用户的识别。对于客户端的每次请求,服务器都会将Cookie发送到客户端,在客户端可以进行保存,以便下次使用
- session:在服务器端会创建一个session对象,产生一个sessionID来标识这个session对象,然后将这个sessionID放入到Cookie中发送到客户端,下一次访问时,sessionID会发送到服务器,在服务器端进行识别不同的用户,Session是依赖Cookie的,如果Cookie被禁用,那么session也将失效, session默认的会话时长为30分钟
- url重写:就是在URL结尾添加一个附加数据以标识该会话,把会话ID通过URL的信息传递过去,以便在服务端进行识别不同的用户
- 隐藏表单域:将会话ID添加到HTML表单元素中提交到服务器,此表单不再客户端显示
- ip地址
-
JS的基本数据类型和引用数据类型
基本数据类型:undefined、null、boolean、number、string、symbol
引用数据类型:object、array、function -
js有哪些内置对象
- Object 是 JavaScript 中所有对象的父对象
- 数据封装类对象:Object、Array、Boolean、Number 和 String
- 其他对象:Function、Arguments、Math、Date、RegExp、Error
- 说几条写JavaScript的基本规范
- 不要在同一行声明多个变量
- 请使用===/!==来比较true/false或者数值
- 使用对象字面量替代new Array这种形式
- 不要使用全局函数
- Switch语句必须带有default分支
- If语句必须使用大括号
- for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污
- 代码缩进,4个空格
- 代码段使用{}包裹
- 语句结束使用分号
- 变量和函数在使用前进行声明
- 以大写字母开头命名构造函数,全大写命名常量
- 规范定义JSON对象,补全双引号
- 用{}和[]声明对象和数组
- JavaScript有几种类型的值
- 栈:原始数据类型(Undefined,Null,Boolean,Number、String)
- 堆:引用数据类型(对象、数组和函数)
- 两种类型的区别是:存储位置不同;
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
- javascript创建对象的几种方式
- 对象字面量的方式
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
- 通过”构造函数“方式创建
var obj = new 函数名();
- 通过object方式创建
var person = new Object();
person.name = "dongjc";
person.age = 32;
person.Introduce = function () {
alert("My name is " + this.name + ".I'm " + this.age);
};
person.Introduce();
- null,undefined 的区别
- undefined 表示不存在这个值
- undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined。例如变量被声明了,但没有赋值时,就等于undefined
- null 表示一个对象被定义了,值为“空值”
- null : 是一个对象(空对象, 没有任何属性和方法)。例如作为函数的参数,表示该函数的参数不是对象
- 在验证null时,一定要使用 === ,因为 ==无法分别null 和 undefined
-
javascript 代码中的"use strict";是什么意思
use strict是一种ECMAscript 5 添加的**(严格)运行模式**,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为 -
JSON 的了解
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式
- 它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
- JSON字符串转换为JSON对象:
var obj =eval('('+ str +')'); var obj = str.parseJSON(); var obj = JSON.parse(str);
- JSON对象转换为JSON字符串:
var last=obj.toJSONString(); var last=JSON.stringify(obj);
- js延迟加载的方式有哪些
- 设置< script>属性 defer=“defer” (脚本将在页面完成解析时执行)
- 动态创建 script DOM:document.createElement(‘script’);
- XmlHttpRequest 脚本注入
- 延迟加载工具 LazyLoad
- 同步和异步的区别
- 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
- 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
- defer和async
- defer并行加载js文件,会按照页面上script标签的顺序执行
- async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行
- 说说严格模式的限制
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 禁止this指向全局对象
- 不能使用前缀0表示八进制数,否则报错
- attribute和property的区别是什么
- attribute是dom元素在文档中作为html标签拥有的属性
- property就是dom元素在js中作为对象拥有的属性
- 对于html的标准属性来说,attribute和property是同步的,是会自动更新的
- 但是对于自定义的属性来说,它们是不同步的
- 谈谈你对ES6的理解
- 新增模板字符串 ``,
${}
- 箭头函数
- for-of(用来遍历数据—例如数组中的值。)
- arguments对象可被不定参数和默认参数完美代替
- ES6将promise对象纳入规范,提供了原生的Promise对象
- 增加了let和const命令,用来声明变量
- 增加了块级作用域。let命令实际上就增加了块级作用域
- 引入module模块的概念
- 面向过程和面向对象
面向过程和面向对象最明显的区别就是,面向对象是按照要完成的功能来实现的,而面向过程是按照解决这个问题的步骤来实现的。
- 面向对象编程思想就是:使用对象,类,继承,封装等基本概念来进行程序设计
- 优点
- 可读性高,易维护
- 易扩展
- 开发工作的重用性、继承性高,降低重复工作量。
- 缩短了开发周期
- 如何通过JS判断一个数组
-
instanceof运算符:instanceof是用来测试一个对象是否在其原型链原型构造函数的属性。
var arr = []; arr instanceof Array; // true
-
constructor属性,返回对创建此对象的数组函数的引用,就是返回对象对应的构造函数。
var arr = []; arr.constructor == Array; //true
-
ES5新增方法isArray()
var a = new Array(123); var b = new Date(); console.log(Array.isArray(a)); //true console.log(Array.isArray(b)); //false
-
Object.prototype.toString.call(arr) === ‘[Object Array]’
- map与forEach的区别
-
forEach(item, index, Array),是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item、数组索引index、和当前遍历数组Array。没有返回值。
-
map方法,基本用法与forEach一致,但是不同的,它会返回一个新的数组,所以在callback需要有return值,如果没有,会返回undefined。
var arr = [1,23,3]; arr.map(function(value,index,array){ //do something return XXX })
- 箭头函数与普通函数的区别
// 普通函数
function func(){
// code
}
// 箭头函数
let func=()=>{
// code
}
- 箭头函数都是匿名函数
普通函数可以有匿名函数,也可以有具体名函数,但是箭头函数都是匿名函数。
// 具名函数
function func(){
// code
}
// 匿名函数
let func=function(){
// code
}
// 箭头函数全都是匿名函数
let func=()=>{
// code
}
-
箭头函数不能用于构造函数,不能使用new
普通函数可以用于构造函数,以此创建对象实例function Person(name,age){ this.name=name; this.age=age; } let admin=new Person("恩诺小弦",18); console.log(admin.name); console.log(admin.age);
-
箭头函数中this的指向不同
在普通函数中,this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例 -
.箭头函数不能Generator函数,不能使用yeild关键字。
-
箭头函数不具有prototype原型对象。
-
箭头函数不具有super。
-
箭头函数不具有new.target。
- 异步编程的实现方式
- 回调函数
- 优点:简单、容易理解
- 缺点:不利于维护,代码耦合高
- 事件监听(采用时间驱动模式,取决于某个事件是否发生):
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
- 缺点:事件驱动型,流程不够清晰
- 发布/订阅(观察者模式)
- 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
- Promise对象
- 优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
- 缺点:编写和理解,相对比较难
- Generator函数:function * a() / yield / next
- 优点:函数体内外的数据交换、错误处理机制
- 缺点:流程管理不方便
- async函数
- 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
- 缺点:错误处理机制
- 对原生Javascript了解程度
数据类型、运算、对象、Function、继承、闭包、作用域、原型链、事件、RegExp、JSON、Ajax、DOM、BOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、Canvas、ECMAScript
- Js动画与CSS动画区别及相应实现
- CSS3的动画的优点:
- 在性能上会稍微好一些,浏览器会对CSS3的动画做一些优化
- 代码相对简单
- 缺点
- 在动画控制上不够灵活
- 兼容性不好
JavaScript的动画正好弥补了这两个缺点,控制能力很强,可以单帧的控制、变换,同时写得好完全可以兼容IE6,并且功能强大。对于一些复杂控制的动画,使用javascript会比较靠谱。而在实现一些小的交互动效的时候,就多考虑考虑CSS吧
- JS 数组和对象的遍历方式,以及几种方式的比较
-
for
使用临时变量len,将长度缓存起来,避免重复获取数组长度var arr = [1, 2, 3, 4, 5, 6] for(var i = 0, len = arr.length; i < len; i++) { console.log(arr[i]) } // 1 2 3 4 5 6
-
for - in
效率最低var arr = ['我', '是', '谁', '我', '在', '哪'] for(var key in arr) { console.log(key) } // 0 1 2 3 4 5 索引
-
for…of…(ES6)
不能循环对象var arr = ['我', '是', '谁', '我', '在', '哪'] for(var key of arr) { console.log(key) }
-
forEach
- 数组里的元素个数有几个,该方法里的回调就会执行几次
- 第一个参数是数组里的元素,第二个参数为数组里元素的索引,第三个参数则是它自己
- 数组自带的遍历方法,虽然使用频率略高,但是性能仍然比普通循环略低
var arr = [1, 2, 3, 4, 5, 6] arr.forEach(function (item, idnex, array) { console.log(item) // 1 2 3 4 5 6 console.log(array) // [1, 2, 3, 4, 5, 6] })
-
map
- 遍历每一个元素并且返回对应的元素(可以返回处理后的元素) (map 映射 一一 对应)
- 返回的新数组和旧数组的长度是一样的
- 使用比较广泛,但其性能还不如 forEach
var arr = [1, 2, 3, 4, 5, 6] var newArr = arr.map(function (item, idnex) { return item * item }) console.log(newArr) // [1, 4, 9, 16, 25, 36]
-
filter
遍历数组,过滤出符合条件的元素并返回一个新数组var arr = [ { id: 1, name: '买笔', done: true }, { id: 2, name: '买笔记本', done: true }, { id: 3, name: '练字', done: false } ] var newArr = arr.filter(function (item, index) { return item.done }) console.log(newArr) // [{ id: 1, name: '买笔', done: true },{ id: 2, name: '买笔记本', done: true }]
-
some
遍历数组,只要有一个以上的元素满足条件就返回 true,否则返回 falsevar arr = [ { id: 1, name: '买笔', done: true }, { id: 2, name: '买笔记本', done: true }, { id: 3, name: '练字', done: false } ] var bool = arr.some(function (item, index) { return item.done }) console.log(bool) // true
-
every
遍历数组,每一个元素都满足条件 则返回 true,否则返回 falsevar arr = [ { id: 1, name: '买笔', done: true }, { id: 2, name: '买笔记本', done: true }, { id: 3, name: '练字', done: false } ] var bool = arr.every(function (item, index) { return item.done }) console.log(bool) // false
-
find(ES6)
遍历数组,返回符合条件的第一个元素,如果没有符合条件的元素则返回 undefinedvar arr = [1, 1, 2, 2, 3, 3, 4, 5, 6] var num = arr.find(function (item, index) { return item === 3 }) console.log(num) // 3
-
findIndex(ES6)
遍历数组,返回符合条件的第一个元素的索引,如果没有符合条件的元素则返回 -1var arr = [1, 1, 2, 2, 3, 3, 4, 5, 6] var num = arr.findIndex(function (item) { return item === 3 }) console.log(num) // 4
- call() / apply() / bind()
call()和apply()的作用相同,都是用来修改函数中this的指向问题。他们的区别在于接收参数的方式不同。
-
B.call(A, args1,args2):调用一个对象的一个方法,用另一个对象替换当前对象。即:A对象调用B对象的方法
- A:上下文对象
- args1,args2:第二个参数以列表的形式传入。传递给函数的参数必须逐个列举出来
-
B.apply(A, arguments):调用一个对象的一个方法,用另一个对象替换当前对象。即:A对象应用B对象的方法
- A:函数上下文的对象
- arguments:是该函数传入的数组形式的参数
function add(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
-
bind除了返回是函数以外,它的参数和call一样
- [“1”, “2”, “3”].map(parseInt) 答案是多少
[1, NaN, NaN]
- 因为 parseInt 需要两个参数 (val, radix),其中radix 表示解析时用的基数。
- map传了 3个(element, index, array),对应的 radix 不合法导致解析失败。
- JS为什么是单线程?
javascript是单线程,与它的用途有关。作为浏览器脚本语言,js的主要作用是与用户互动以及操作DOM,这决定了它只能是单线程,否则会带来很多复杂的问题。比如说js如果同时有两个线程的话,一个线程在某个DOM节点上面添加内容,而另一个线程则是删除了这个节点,这时浏览器应该以哪个线程为主?
所以,为了避免这种复杂性,从一诞生,javascript就是单线程。
但是为了利用多核CPU的计算能力,HTML5提供了web worker标准,允许js脚本同时创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以这个新的标准并没有改变js单线程的本质。
- JS是单线程的,那它是如何实现异步操作的?
JS是单线程的,但是JS是在浏览器中运行的脚本语言。它的宿主,浏览器可不是单线程。
- 事件轮询 Event Loop
先看一段代码
console.log('hi');
setTimeOut(function cb1(){
console.log('cb1')
})
console.log('bye')
执行后的打印顺序就像下面图中的结果一样。
- 同步代码,一行一行放在call stack 中执行
- 遇到异步代码,会先记录下,等待时机(定时,网络请求等)
- 时机到了,就移动到callback queue
- 如果call stack为空(即同步代码执行完), event loop开始工作。(
每次call stack清空(即每次轮询结束),即同步任务执行完。都是DOM重新渲染的机会,DOM结构如有改变则重新渲染。然后去触发下一次Event Loop
) - 轮询查找callback queue,有则移动到call stack中执行
- 然后继续轮询查找(永动机一样)
DOM事件和event loop
- 异步(setTimeOut, ajax等)使用回调,基于event loop
- dom事件也使用回调,基于event loop
- js是单线程的,而且和DOM渲染公用同一个线程。所以js执行的时候,得留一些时机供DOM渲染
- 宏任务(macroTask)和微任务(microTask)
- 宏任务:DOM渲染后触发。宏任务(Web APIs)是由浏览器规定的。
- script
- setTimeOut
- setInterval
- ajax
- I/O
- DOM事件
- 微任务:DOM渲染前触发。微任务是由ES6语法规定的。
- Promise
- async/await
- process.nextTick
- Object.observe
- MutationObserver
微任务执行比宏任务要早
demo👇
console.log(100);
setTimeOut(() => {
console.log(200);
})
Promise.resolve().then(() => {
console.log(300);
})
console.log(400);
// 100
// 400
// 300
// 200
console.log('hi');
setTimeOut(function cb1(){
console.log('cb1')
})
console.log('bye')
执行如下👇
所以,一次正确的event loop顺序是这样的:
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的响应界面响应,我们可以把操作 DOM 放入微任务中。
-
Vue的双向绑定数据的原理
vue.js是采用 数据劫持 结合 发布者-订阅者模式 的方式,来通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 -
如何快速让一个数组乱序? Math.random()
var arr = [1,2,3,4,5,6,7,8,9,10]; arr.sort(function(){ return Math.random()-0.5; }) console.log(arr);
-
怎样获取到页面中的所有checkbox?
var domList = document.getElementsByTagName('input');
var checkboxList = [];
var len = domList.length;
while(len--){ // //使用while的效率会比for循环更高
if(domList[len].type === 'checkbox'){
checkboxList.push(domList[len]);
}
}
或者
var resultArr = [];
var input = document.querySelectorAll('input');
for(var i = 0; i < input.length; i++){
if(input[i].type == 'checkbox'){
resultArr.push(input[i])
}
}
- DOM
DOM(文档对象模型)是针对HTML和XML文档的一个API。DOM描绘了一个层次化的节点树,允许开发人员添加,移除和修改页面的某一部分。可以说,DOM是前端开发中最重要的一个渲染API,它决定了页面的样式结构的走向。
-
节点层次
DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构,总共有12种节点类型,这些类型都继承自一个基类型,因为其中有些节点只用于XML,这块只针对HTML的节点做介绍- 文档节点(document):表示HTML文档,也称为根节点,HTML文档的文档节点只有一个子节点,即元素,称之为文档元素
- 元素节点(element):对应网页的HTML标签元素
- 特性节点(attribute):对应HTML标签属性
- 文本节点(text):代表网页中HTML标签内容
- 注释节点(comment):表示网页中的HTML注释
- 文档类型节点(documentType):包含着与文档的doctype有关的所有信息
- DTD声明节点(notation):代表DTD中声明的符号
js中的所有节点类型都继承自Node类型,因此所有的节点类型都共享着相同的基本属性和方法。
-
节点关系
- childNodes属性:每个节点都有一个childNodes属性,其中保存一个NodeList对象。NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。NodeList对象的特别之处在于它是动态的,即DOM的结构变化能够自动反应在NodeList。
- parentNode属性:指向该节点在文档树中的父节点
- previousSibling属性:指向该节点在文档树中的前一个同胞节点
- nextSibiling属性:指向该节点在文档树中的后一个同胞节点
- firstChild属性:指向该节点在文档树中的第一个子节点
- lastChild属性:指向该节点在文档树中的最后一个子节点
-
操作节点
- appendChild():用于向childNodes列表的末尾添加一个节点
- insertBefore():把节点放到childNodes列表中某个特定的位置上
- replaceChild():替换特定节点,接受两个参数:要插入的节点和要替换的节点
- removeChild():移除特定节点
- cloneNode():创建调用这个方法的节点的一个完全相同的副本。(接受一个布尔值参数,表示是否执行深复制。在参数为true的情况下,执行深复制,也就是复制节点及整个子节点树,在参数为false的情况下,执行浅复制,也就是复制节点本身。)
- normalize():规范化文档树中的文本节点,如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点
- createElement():创建一个具体的元素
- createDocumentFragment():创建一个DOM片段
- createTextNode():创建一个文本节点
- document 和 element
-
document类型
在浏览器中,document对象是HTMLDocument(继承自Document)的一个实例,表达整个HTML页面。而且,document对象也可以作为window对象的一个属性,因此可以将其作为全局对象来访问。- 文档子节点:
- document.documentElement:指向HTML页面中的html元素
- document.body:指向HTML页面中的body元素
- 查找元素:
- getElementById():通过元素id来查找,返回一个htmlElement对象
- getElementsByTagName():通过元素标签名来查找,返回htmlCollections对象
- getElementsByName():通过元素name来查找,返回htmlCollections对象
- 文档写入:document可以将输入流写入到网页中
- document.write():把输入流的文本写入到网页中
- document.writeIn():在输入流的字符串文本末尾添加一个换行符
\n
在写入到网页中 - document.open():擦除当前html文档的内容,开始一个新的文档,新文档用write()或writeIn()来编写
- document.close():关闭一个由document.open()方法打开的输入流
如果在文档加载结束后即页面被呈现之后再调用document.write()方法的话,那么输出的内容会重写整个页面。
- 文档子节点:
-
element类型
element类型用于表现XML和HTML元素。提供了对元素标签名,子节点及特性的访问。要访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性,两者会返回相同的值。var div1 = document.getElementsByTagName('div'); div1.nodeName === div1.tagName; // true
- HTML元素
所有HTML元素都由HTMLElement类型表示,HTMLElement类型直接继承自Element并添加了一些属性:- id:元素在文档中的唯一标示符
- title:有关元素的附加说明信息。一般通过工具提示条来显示
- lang:元素内容的语言代码
- className:与元素的class特性对应,即为元素指定的class类
- 特性的相关方法
- getAttribute():获取特性
- setAttribute():设置特性
- removeAttribute():移除特性
- 创建元素:document.createElement()
var div = document.createElement('div');
- 元素的子节点:element.childNodes
比如用 document.body.childNodes 来获取body元素的子节点。
- HTML元素
- 正则表达式构造函数var reg=new RegExp(“xxx”)与正则表达字面量var reg=//有什么不同?
当使用 RegExp() 构造函数时,不仅需要转译引号(即\”表示”),还需要双反斜杠(即 \ \表示一个\)。使用正则表达字面量的效率更高。
- 原生JS的window.onload与Jquery的$(document).ready(function(){})有什么不同?
- window.onload() 方法是必须等到页面内包括图片的所有元素加载完毕后才执行。
- $(document).ready() 方法是DOM结构绘制完毕后就执行,不必等到加载完毕。
- 数组去重的方法
-
1 […new Set(arr)]
let arr = [1, 2, 2, 3, 4, 4, 5, 6, 6]; let newArr = [...new Set(arr)]; console.log(newArr); // [1, 2, 3, 4, 5, 6]
-
2 Array.from(new Set())
let arr = [1, 2, 2, 3, 4, 4, 5, 6, 6]; let newArr = Array.from(new Set(arr)); console.log(newArr); // [1, 2, 3, 4, 5, 6]
-
Map + filter
let arr = [1, 2, 2, 3, 4, 4, 5, 6, 6]; let res = new Map(); let newArr = arr.filter(i => !res.has(i) && res.set(i, 1)) console.log(newArr); // [1, 2, 3, 4, 5, 6]
-
for循环嵌套,利用splice去重
let arr = [1, 2, 2, 3, 4, 4, 5, 6, 6]; for(let i = 0; i < arr.length; i++){ for(let j = i+1; j < arr.length; j++){ if(arr[j] === arr[i]){ arr.splice(j, 1); j--; } } } console.log(arr);
-
建新数组,利用indexOf去重
let arr = [1, 2, 2, 3, 4, 4, 5, 6, 6]; let newArr = []; for(var i = 0; i < arr.length; i++){ if(newArr.indexOf(arr[i]) === -1){ newArr.push(arr[i]); } }
- 请简单实现双向数据绑定mvvm
<input id="input" />
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value){
input.value = value;
this.value = value;
}
});
input.onChange = function(e){
data.text = e.target.value;
}
- javascript 对生命周期的理解
- 当创建一个对象时,javascript会自动为该对象分配适当的内存
- 垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量
- 如果被引用数量为0时,或唯一引用是循环,那么该对象的内存即可回收
- 如何编写高性能的javascript
- 遵循严格模式 “use strict”
- js脚本放在页面底部,加快渲染页面
- js脚本成组打包,减少请求
- 尽量使用局部变量来保存全局变量
- 尽量减少使用闭包
- 减少对象成员嵌套
- 缓存DOM节点的访问
- 浏览器的渲染过程
- 解析HTML构建DOM树,并请求css/image/js
- css文件下载完成,开始构建CSSOM(CSS树)
- CSSOM构建完成后和DOM树一起生成渲染树render tree
- 布局(layout):计算出每个节点在屏幕中的位置
- 显示(painting):通过显卡把页面画到屏幕上
- DOM树和渲染树的区别
- DOM树和HTML标签一一对应,包括head和隐藏标签
- 渲染树不包括head和隐藏标签,大段文本的每一行都是一个独立的节点,每一个节点都有对应的css属性
- script的位置是否会影响首屏显示时间?
- 在解析HTML生成DOM时,js文件的下载是并行的,不需要dom处理script节点。因此,
script的位置不会影响首屏显示的开始时间
。 - 浏览器解析html是自上而下的线性过程,script作为html的一部分同样遵循这个原则,因此,
script会延迟DOMContentLoad
,只显示其上部分首屏内容,从而影响首屏显示的完整时间
。
-
函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明。
-
在一个DOM上同时绑定两个点击事件,一个用捕获,一个用冒泡,事件会执行几次?是先执行捕获还是先执行冒泡?
- 该DOM上的事件如果被触发,会执行两次(执行次数等于绑定次数)
- 如果该DOM是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
- 如果该DOM是处于事件流中的非目标元素,则先执行捕获,后执行冒泡
- 函数节流及其应用场景和原理
函数节流(throttle):是指阻止一个函数在短时间内连续被调用,只有当上一次函数被执行后达到规定的时间间隔,才能进行下一次调用。但要保证一个累计最小的时间间隔。
函数节流用于onresize, onscroll等短时间内会多次触发的事件。
函数节流的原理:使用定时器做时间节流。当触发一个事件时,先用setTimeout 让这个事件延迟一段时间再执行,如果在这个事件间隔内又触发了事件,就用clearTimeout来清除原来的定时器。再用一个新的 setTimeout 定时器重复以上流程。
- 区分什么是“客户区坐标”、“页面坐标”、“屏幕坐标”
- 客户区坐标:鼠标指针在可视区中的水平坐标clientX和垂直坐标clientY
- 页面坐标:鼠标指针在页面布局中的水平坐标pageX和垂直坐标pageY
- 屏幕坐标:设备物理屏幕的水平坐标screenX和垂直坐标screenY
- 如何获取一个DOM元素的绝对位置?
- elem.offsetLeft:返回元素相对于其定位父级左侧的距离
- elem.offsetTop:返回元素相对于其定位父级顶部的距离
- elem.getBoundingClientRect:返回一个DOMRect对象,包含一组描述边框的只读属性,单位像素
- Javascript垃圾回收方法
- 标记清除(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
- 引用计数(reference counting)
- 引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间
- 如何删除一个cookie?
- 将时间设为当前时间往前一点
var date = new Date();
// setDate()方法用于设置一个月的某一天
date.setDate(date.getDate() - 1);//真正的删除
- 可以设置cookie的过期时间expires,即
document.cookie = 'user='+ encodeURIComponent('name') + ';expires = ' + new Date(0)
- 把< script>放在< /body>之前和之后有什么区别?浏览器会如何解析它们?
- 按照HTML标准,< /body>结束后出现< script>或任何元素的开始标签,都会解析错误
- 虽然不符合HTML标准,但浏览器会自动容错,使实际效果与写在之前没有区别
- 浏览器的容错机制会忽略
- JS中,调用函数的方式有
- 方法调用模式: Foo.foo(arg1, arg2);
- 函数调用模式: foo(arg1, arg2);
- 构造器调用模式: (new Foo())(arg1, arg2);
- call/applay调用模式:Foo.foo.call(that, arg1, arg2); / Foo.foo.call(that, [arg1, arg2]);
- bind调用模式 : Foo.foo.bind(that)(arg1, arg2)();
- array.slice() / array.splice()
- array.slice(start, end) :读取数组指定的元素,不会对原数组进行修改
- start 指定选取开始位置(含)
- end 指定选取结束位置(不含)
- array.splice(index, count, [insert Elements]):操作数组指定的元素,会修改原数组,返回被删除的元素
- index 是操作的起始位置
- count = 0 插入元素,count > 0 删除元素
- [insert Elements] 向数组新插入的元素
- MVVM
- 在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合
- 在 MVVM 中,
UI 是通过数据驱动的
,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。 - ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel
- 在 MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持
- 数据劫持
Vue中使用了Object.defineProperty()来实现数据双向绑定。通过这个函数来监听set / get 的事件
- Web应用从服务器主动推送data到客户端的方式有:
- ajax轮询
- html5服务器推送事件。(new EventSource(SERVER_URL)).addEventListener(“message”, func);
- html5 websocket
- (new WebSocket(SERVER_URL)).addEventListener(“message”, func);
- 图片的预加载和懒加载
- 预加载:提前加载图片,当用户需要查看图片时可以从本地缓存中渲染。预加载则会增加服务器前端压力。
- 懒加载
- 目的:主要是作为服务器前端的优化,减少请求数或延迟请求数。懒加载对服务器前端有一定的缓解压力作用。
- 原理是:只加载自定义区域(通常是可视区域)内需要加载的东西,对于图片来说,先设置图片标签的src属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为src属性,这样图片就会去下载资源,实现了图片的懒加载。
- 排序
-
冒泡排序:每次比较相邻的两个数,如果后一个比前一个小,交换位置
var arr = [3, 1, 4, 6, 5, 7, 2]; function bubbleSort(){ for(var i = 0; i < arr.length; i++){ for(var j = 0; j < arr.length; j++){ if(arr[j+1] < arr[i]){ var temp; temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr; } console.log(bubbleSort(arr));
-
快排:采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边
var arr = [3, 1, 4, 6, 5, 7, 2]; function quickSort(arr) { if(arr.length === 0){ return []; } var cIndex = Math.floor(arr.length / 2); // 取最中间的那个值 var c = arr.splice(cIndex, 1); var l = []; var r = []; for(var i = 0; i < arr.length; i++){ if(arr[i] < c){ l.push(arr[i]); }else{ r.push(arr[i]); } } return quickSort(l).concat(c, quickSort(r)) } console.log(quickSort(arr));
- 微信小程序和vue的区别
-
生命周期不一样,微信小程序生命周期比较简单
- onLaunch: 初始化小程序时触发,全局只触发一次
- onShow: 小程序初始化完成或用户从后台切换到前台显示时触发
- onHide: 用户从前台切换到后台隐藏时触发
- onError: 小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
- 后台: 点击左上角关闭,或者按了设备 Home 键离开微信,并没有直接销毁,而是进入后台
- 前台:再次进入微信或再次打开小程序,相当于从后台进入前台。
-
数据绑定也不同,微信小程序数据绑定需要使用
{{}}
,vue 直接:就可以 -
显示与隐藏元素,vue中,使用
v-if 和 v-show
控制元素的显示和隐藏,小程序中,使用wx-if 和hidden
控制元素的显示和隐藏 -
事件处理不同,小程序中,全用
bindtap(bind+event)
,或者catchtap(catch+event)
绑定事件,vue:使用v-on:event
绑定事件,或者使用@event
绑定事件 -
数据双向绑定也不也不一样在 vue中,只需要在表单元素上加上
v-model
,然后再绑定 data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是 vue非常 nice 的一点。微信小程序必须获取到表单元素,改变的值,然后再把值赋给一个 data中声明的变量。
- 负载均衡
多台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用 。
- http重定向负载均衡:调度者根据策略选择服务器以302响应请求,缺点只有第一次有效果,后续操作维持在该服务器
- dns负载均衡:解析域名时,访问多个ip服务器中的一个(可监控性较弱)
- 反向代理负载均衡:访问统一的服务器,由服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能受到 服务器群的数量
- CDN
-
内容分发网络。基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
-
静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie
- 内存泄漏
程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。
- JS中可能出现的内存泄漏情况:结果:变慢,崩溃,延迟大等。原因:
- 全局变量
- dom清空时,还存在引用。
- IE中使用闭包
- 定时器未清除
- 子元素存在引起的内存泄漏
- 怎样避免内存泄漏
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
- 注意程序逻辑,避免“死循环”之类的
- 避免创建过多的对象, 原则:不用了的东西要及时归还
- 减少层级过多的引用
- 前后端路由的差别
- 后端每次路由请求都是重新访问服务器
- 前端路由实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合
- 数组降维
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
- 怎么判断页面是否加载完成?
load
事件触发代表页面中的DOM / CSS / JS / 图片 已经加载完成DOMContentLoaded
事件触发代表初始的HTML被完全加载和解析,不需要等待CSS / JS / 图片加载
- Babel原理
Babel本质就是编译器。当代码转换为字符串生成AST,对AST进行转变最后再生成新的代码。
分为三步:
- 词法分析生成token
- 语法分析生成AST
- 遍历AST,根据插件变换相应的节点,最后把AST转换为代码
- 防抖和节流
使用场景: 在滚动事件中需要做个复杂计算 或 实现一个按钮的防二次点击操作。
防抖和节流都是防止函数多次调用,假设一个用户一直在触发一个函数,且每次触发事件的间隔小于wait,这时:
- 防抖是只调用一次。防抖动是将多次执行变为最后一次执行
- 节流是每隔一段时间调用一次(参数是wait),节流是将多次执行变成每隔一段时间执行