前端面试题 - JS
前端 JS 面试题积累
JS 获取 URL 中的 Query 参数
getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = routerStore.location.search.substr(1).match(reg);
if (r !== null) {
return unescape(r[2]);
}
return null;
}
// 调用的时候传递 query 参数的 key 值进去
// 例如需要获取 url 中 id 对应的值
var id = getQueryString('id')
call() 和 apply() 的异同
call 和 apply 的基本作用都是改变对象执行的上下文
call 的基本使用
function.call(obj [, arg1 [, arg2[, [, argN]]]]])
说明
- 调用 call 的对象必须是一个函数 function
- call 的第一个参数将会是 function 改变上下文后指向的对象,如果不传,将会默认是全局对象,在浏览器中就是 window
- 第二个参数开始可以接收任意个参数,这些参数将会作为 function 的参数传入 function
- 调用 call 的方法会立即执行
apply 的基本使用
function.apply(obj [, argArray])
说明
- 与 call 方法的使用基本一致,但是只接收两个参数,其中第二个参数必须是一个数组或者类数组
两个方法的相同点
都能够改变方法的执行上下文(执行环境),将一个对象的方法交给另一个对象来执行,并且是立即执行
两个方法的不同点
call 方法从第二个参数开始可以接收任意个参数,每个参数会映射到相应位置的 function 的参数上,可以通过参数名调用,但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 function 对应的第一个参数上,之后参数都为空
apply 方法最多只有两个参数,第二个参数接收数组或者类数组,但是都会被转换成类数组传入 function 中,并且会被映射到 function 对应的参数上
两个参数如何选择
根据要传入的参数进行选择,不需要传参或者只有一个参数时,用 call。当要传入多个对象时,用 apply
数组和类数组
数组的特征
- 可以通过角标调用,如 array[0]
- 具有长度属性 length
- 可以通过 for 循环和 forEach 方法进行遍历
类数组具备的特征应该与数组基本相同,例如下面这个对象就是一个类数组
var arrayLike = {
0: 'item1',
1: 'item2',
2: 'item3',
length: 3
}
类数组 arrayLike 可以通过角标进行调用,具有 length 属性,同时也可以通过 for 循环进行遍历 那么问题来了,如何让类数组使用 forEach
// dom 树其实就是一个类数组结构
[].forEach.call(document.getElementsByTagName("div"), (item) => console.log(item))
[].forEach.apply(document.getElementsByTagName("div"), [(item) => console.log(item)])
数组方法
push(): 尾部添加
pop(): 尾部删除
unshift(): 头部添加
shift(): 头部删除
添加、删除、替换、插入某个节点的方法
对 dom 树节点进行操作
添加: obj.appendChild
删除: obj.removeChild
替换: obj.replaceChild
插入: obj.innersetBefore
同源策略
JS 同源策略:一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合
instanceof
instanceof 用于判断一个变量是否属于某个对象的实例
instanceof 运算符可以用来判断某个构造函数的 prototype 属性是否存在于另外一个要检测对象的原型链上
a instanceof b ? true : false;
实例:
var a = new Array();
console.log(a instanceof Array); // true
console.log(a instanceof Object) // true
JS 闭包
定义和用法:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的其它变量,如果返回的这个函数在外部被执行,就产生了闭包
表现形式:使函数外部能够调用函数内部定义的变量
实例:
var count = 10; // 全局作用域 标记为 flag1
function add() {
var count = 0; // 函数全局作用域 标记为 flag2
return function() {
count += 1; // 函数的内部作用域
console.log(count);
}
}
var s = add()
s(); // 输出 1
s(); // 输出 2
根据作用域链的规则,底层作用域没有声明的变量,会向上一级找,找到就返回,没找到就一直找,直到 window 的变量,没有就返回 undefined。这里明显 count 是函数内部的 flag2 的那个 count
使用闭包需要注意的点
- 滥用闭包,会造成内存泄漏:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
- 会改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
变量声明提升和函数声明提升
变量声明提升
变量定义
可以使用 var 定义变量,变量如果没有赋值,那变量的初始值为 undefined
变量作用域
变量作用域指变量起作用的范围。变量分为*全局变量*和*局部变量*。全局变量在全局都拥有定义;而局部变量只能在函数内有效
*在函数体内,同名的局部变量或者参数的优先级会高于全局变量*。也就是说,如果函数内存在和全局变量同名的局部变量或者参数,那么全局变量将会被局部变量覆盖
所有不使用 var 定义的变量都视为全局变量
函数作用域和声明提前
JavaScript 的函数作用是指在函数内声明的所有变量在函数体内始终是有定义的,也就是说变量在声明之前已经可用,所有这特性称为声明提前(hoisting),即 JavaScript 函数里的所有声明(只是声明,但不涉及赋值)都被提前到函数体的顶部,而变量赋值操作留在原来的位置
*注释*:声明提前是在 JavaScript 引擎的预编译时进行,是在代码开始运行之前
如下面的例子:
var scope = 'global';
function f() {
console.log(scope);
var scope = 'local';
console.log(scope);
}
// 第一次输出 undefined,第二次输出 local
函数声明提升
*注*:只有函数声明式语法才会出现函数提升
函数的两种创建方式
(1)函数声明语法
// 函数声明语法
f('superman');
function f(name) {
console.log(name);
}
// 结果会输出 superman
(2)函数表达式语法
// 函数表达式语法
f('superman');
var f= function(name) {
console.log(name);
}
// 上述代码会报错
函数声明提升
函数声明提升,函数声明语句将会被提升到外部脚本或者外部函数作用域的顶部(跟变量提升非常类似)。正是因为这个特征,所以可以把函数声明放在调用它的语句后面
值得注意的是,函数声明提升在变量声明提升的前面
var getName = function() {
console.log(2);
}
function getName () {
console.log(1);
}
getName();
可能会有人觉得最后输出的结果是 1。让我们来分析一下,这个例子涉及到了变量声明提升和函数声明提升。正如前面说到的函数声明提升,函数声明 function getName() {...}
的声明会被提前到顶部。而函数表达式 var getName = function() {...}
则表现出变量声明提升。因此在这种情况下,getName 也是一个变量,因此这个变量的声明也将提升到底部,而变量的赋值依然保留在原来的位置。因此上面的函数可以转换成下面的样子
var getName; // 变量声明提升
function getName() { // 函数声明提升到顶部
console.log(1);
}
getName = function() { // 变量赋值依然保留在原来的位置
console.log(2);
}
getName(); // 最终输出:2
介绍 js 的基本数据类型
最基本的有:Undefined、Null、Boolean、Number、String、Object
ECMAScript 2015 新增 Symbol(创建后独一无二且不可变的数据类型)
更多 Symbol 的知识可以参考 传送门 方法”)
介绍 js 有哪些内置对象?
Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error
参考 传送门
说几条写 JavaScript 的基本规范?
- 不要在同一行声明多个变量
- 使用 === / !== 来比较 true / false 或者数值
- 使用对象字面量替代 new Array 这种形式
- 不要使用全局函数
- Switch 语句必须带有 default 分支
- 函数不应该有时候有返回值,有时候没有返回值
- for 循环必须使用大括号
- if 语句必须使用大括号
- for-in 循环中的变量应该使用 var 关键字明确限定作用域,从而避免作用域污染
JavaScript原型,原型链?有什么特点?
每个对象都会在其内部初始化一个属性,就是 prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这个 prototype 又会有自己的 prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
关系:instance.constructor.prototype = instance.proto
特点:
JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性的时,Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的 prototype 对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
看下面的🌰
function Func() {}
Func.prototype.name = "Sean";
Func.prototype.getInfo = function() {
return this.name;
}
var person = new Func(); // 现在可以参考 var person = Object.create(oldObject);
console.log(person.getInfo()); // 它拥有了 Func 的属性和方法
// "Sean"
console.log(Func.prototype);
// Func {name = "Sean", getInfo = function()}
JavaScript 有几种类型的值?你能画一下他们的内存图吗?
- 栈:原始数据类型(Undefined,Null,Boolean,Number,String)
- 堆:引用数据类型(对象、数组和函数)
两种类型的区别是:存储位置不同
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
如下图所示
如何将字符串转化为数字?例如 ‘12.3b’
- parseFloat(‘12.3b’);
- 正则表达式,’12.3b’.match(/(\d)+(.)?(\d)+/g)[0] * 1,但是这个不太靠谱,提供一种思路而已
如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为 ‘12,000,000.11’?
看下面的🌰
function convert(num) {
return num && num
.toString()
.replace(/(\d)(?=(\d{3})+\.)/g, function($1, $2) {
return $2 + ',';
});
}
如何实现数组的随机排序?
看下面的🌰
方法一:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function randSort1(arr) {
for(var i = 0, len = arr.length; i < len; i++ ) {
var rand = parseInt(Math.random() * len);
var temp = arr[rand];
arr[rand] = arr[i];
arr[i] = temp;
}
return arr;
}
console.log(randSort1(arr));
方法二:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function randSort2(arr) {
var mixedArray = [];
while(arr.length > 0) {
var randomIndex = parseInt(Math.random() * arr.length);
mixedArray.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return mixedArray;
}
console.log(randSort2(arr));
方法三:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function() {
return Math.random() - 0.5;
})
console.log(arr);
Javascript 如何实现继承?
- 构造继承
- 原型继承
- 实例继承
- 拷贝继承
原型 prototype 机制或 apply 和 call 方法去实现较简单,建议使用构造函数与原型混合方式
看下面的🌰
function Parent() {
this.name = 'jia';
}
function Child() {
this.age = 28;
}
Child.prototype = new Parent(); // 继承了 Parent,通过原型
var demo = new Child();
alert(demo.age); // 28
alert(demo.name); // jia 得到被继承的属性
JavaScript 继承的几种实现方式?
*注*:这个部分还是挺重要的,要好好看一下
Javascript 创建对象的几种方式?
Javascript 创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用 JSON;但写法有很多种,也能混合使用
- 对象字面量的方式
person = {firstname: "Yizhen", lastname: "Jia", age:25, eyecolor: "black"};
- 用 function 来模拟无参的构造函数
function Person() {}
var person = new Person(); // 定义一个 function,如果使用 new "实例化",该 function 可以看作是一个 Class
person.name = "Jia";
person.age = "25";
person.work = function() {
console.log(person.name + " hello...");
}
person.work();
- 用 function 来模拟参构造函数来实现(用 this 关键字定义构造的上下文属性)
function Pet(name, age, hobby) {
this.name = name; // this 作用域:当前对象
this.age = age;
this.hobby = hobby;
this.eat = function() {
console.log("我叫" + this.name + ",我喜欢" + this.hobby + ",是个程序员");
}
}
var maidou =new Pet("麦兜", 25, "coding"); // 实例化、创建对象
maidou.eat(); // 调用 eat 方法
- 用工厂方式来创建(内置对象)
var wcDog = new Object();
wcDog.name = "旺财";
wcDog.age = 3;
wcDog.work = function() {
console.log("我是" + wcDog.name + ",汪汪汪......");
}
wcDog.work();
- 用原型方式来创建
function Dog() {}
Dog.prototype.name = "旺财";
Dog.prototype.eat = function() {
console.log(this.name + "是个吃货");
}
var wangcai = new Dog();
wangcai.eat();
- 用混合方式来创建
function Car(name, price) {
this.name = name;
this.price = price;
}
Car.prototype.sell = function() {
console.log("我是" + this.name + ",我现在卖" + this.price + "万元");
}
var camry = new Car("凯美瑞", 27);
camry.sell();
Javascript 作用链域?
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链
谈谈 This 对象的理解
- this 总是指向函数的直接调用者(而非间接调用者)
- 如果有 new 关键字,this 指向 new 出来的那个对象
- 在事件中,this 指向触发这个事件的对象,特殊的是,IE 中的 attachEvent 中的 this 总是指向全局对象 window
eval 是做什么的?
- 它的功能是把对应的字符串解析成 JS 代码并运行
- 应该避免使用 eval,不安全,非常耗性能(运行 eval 的时候是运行了 2 次,一次解析成 js 语句,一次执行)
- 由 JSON 字符串转换为 JSON 对象的时候可以用 eval,
var obj = eval('('+ str +')');
什么是 window 对象? 什么是 document 对象?
- window 对象是指浏览器打开的窗口
- document 对象是 Document 对象(HTML 文档对象)的一个只读引用,window 对象的一个属性
null,undefined 的区别?
- null 表示一个对象是“没有值”的值,也就是值为“空”
- undefined 表示一个变量声明了没有初始化(赋值)
- undefined 不是一个有效的 JSON,而 null 是
- undefined 的类型(typeof)是 undefined
- null 的类型(typeof)是 object
- Javascript 将未赋值的变量默认值设为 undefined
- Javascript 从来不会将变量设为 null。它是用来让程序员表明某个用 var 声明的变量时没有值的
typeof undefined // "undefined"
undefined:是一个表示”无”的原始值或者说表示”缺少值”,就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined
例如变量被声明了,但没有赋值时,就等于 undefined
typeof null // "object"
null:是一个对象(空对象, 没有任何属性和方法)
例如作为函数的参数,表示该函数的参数不是对象
注意:在验证 null 时,一定要使用 === ,因为 == 无法分别 null 和 undefined
null == undefined // true
null === undefined // false
具体可以参考 传送门
[“1”, “2”, “3”].map(parseInt) 答案是多少?
parseInt() 函数能解析一个字符串,并返回一个整数,需要两个参数 (val, radix)
其中 radix 表示要解析的数字的基数。[该值介于 2 ~ 36 之间。如果省略该参数或其值为 ‘0’,则数字将以 10 为基础来解析。如果它以 ‘0x’ 或 ‘0X’ 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 ‘parseInt()’ 将返回 ‘NaN’]
但此处 map 传了 3 个 (element, index, array),我们重写 parseInt 函数测试一下是否符合上面的规则
function parseInt(str, radix) {
return str + '-' + radix;
};
var a = ["1", "2", "3"];
a.map(parseInt); // ["1-0", "2-1", "3-2"] 不能大于 radix
针对 [3-2] 因为二进制里面,没有数字 3,导致出现超范围的 radix 赋值和不合法的进制解析,才会返回 NaN
所以[“1”, “2”, “3”].map(parseInt) 答案也就是:[1, NaN, NaN]
具体可以参考 传送门返回 [1, NaN, NaN]“)
事件是什么?IE 与火狐的事件机制有什么区别? 如何阻止冒泡?
- 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为
- 事件处理机制:IE 是事件冒泡、Firefox 同时支持两种事件模型,也就是:捕获型事件和冒泡型事件
- 阻止冒泡方法:ev.stopPropagation();(旧 IE 的方法 ev.cancelBubble = true;)
什么是闭包(closure),为什么要用它?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
闭包的特性:
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
// li 节点的 onclick 事件都能正确的弹出当前被点击的 li 索引
<ul id="testUL">
<li>index = 0</li>
<li>index = 1</li>
<li>index = 2</li>
<li>index = 3</li>
</ul>
<script type="text/javascript">
var nodes = document.getElementsByTagName("li");
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = (function(i) {
return function() {
console.log(i);
} // 不用闭包的话,值每次都是 4
})(i);
}
</script>
看下面的🌰
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() {
alert(num);
}
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert(); // 执行结果应该弹出的 667
执行 say667() 后,say667() 闭包内部变量会存在,而闭包内部函数的内部变量不会存在。使得 Javascript 的垃圾回收机制 GC 不会收回 say667() 所占用的资源,因为 say667() 的内部函数的执行需要依赖 say667() 中的变量,这是对闭包作用的非常直白的描述
Javascript 代码中的 “use strict”; 是什么意思?使用它区别是什么?
use strict 是一种 ECMAscript5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行
使 JS 编码更加规范化的模式,消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为
默认支持的糟糕特性都会被禁用,比如不能用 with,也不能在意外的情况下给全局变量赋值
全局变量的显示声明,函数必须声明在顶层,不允许在非函数代码块内声明函数,arguments.callee 也不允许使用
消除代码运行的一些不安全之处,保证代码运行的安全,限制函数中的 arguments 修改,严格模式下的 eval 函数的行为和非严格模式的也不相同
提高编译器效率,增加运行速度。为未来新版本的 Javascript 标准化做铺垫
如何判断一个对象是否属于某个类?
看下面的🌰
// 使用 instanceof
if (a instanceof Person) {
alert('yes');
}
使用 instanceof 有一个缺陷,比如下面的🌰
var test = [1, 2, 3];
console.log(a instanceof Array); // true
console.log(a instanceof Object); // true
可以看到使用 instanceof 不能准确的判断变量类型
Javascript 中检测对象的类型的运算符有:typeof、constructor、instanceof
typeof:typeof 是一个一元运算符,返回结果是一个说明运算数类型的字符串。如:”number”,”string”,”boolean”,”object”,”function”,”undefined”(可用于判断变量是否存在)。但 typeof 的能力有限,其对于 Date、RegExp、Array 类型返回的都是 “object”。所以它只在区别对象和原始类型的时候才有用。要区一种对象类型和另一种对象类型,必须使用其他的方法
instanceof 运算符:instanceof 运算符要求其左边的运算数是一个对象,右边的运算数是对象类的名字或构造函数。如果 object 是 class 或构造函数的实例,则 instanceof 运算符返回 true。如果 object 不是指定类或函数的实例,或者 object 为 null,则返回 false。instanceof 方法可以判断变量是否是数组类型,但是只限同一全局环境之内,在一个页面有多个 iframe 的情况下,instanceof 失效
constructor 属性: JavaScript 中,每个对象都有一个 constructor 属性,它引用了初始化该对象的构造函数,常用于判断未知对象的类型。如给定一个求知的值,通过 typeof 运算符来判断它是原始的值还是对象。如果是对象,就可以使用 constructor 属性来判断其类型,看下面的🌰
var test = [1, 2, 3];
console.log(a.constructor === Array); // true
console.log(a.constructor === Object); // false
Object.prototype.toString.call(); 该方法是目前为止发现的判断一个对象类型的最好的办法
具体可以参考 传送门
new 操作符具体干了什么呢?
- 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型
- 属性和方法被加入到 this 引用的对象中
- 新创建的对象由 this 所引用,并且最后隐式的返回 this
可以用下面的代码表示
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
JavaScript 中,有一个函数,执行对象查找时,永远不会去查找原型,这个函数是?
这个函数是 hasOwnProperty
JavaScript 中 hasOwnProperty 函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员
使用方法:
object.hasOwnProperty(proName);
其中参数 object 是必选项。一个对象的实例。proName 是必选项。一个属性名称的字符串值
如果 object 具有指定名称的属性,那么 JavaScript 中 hasOwnProperty 函数方法返回 true,反之则返回 false
JSON 的了解?
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式
它是基于 JavaScript 的一个子集。数据格式简单, 易于读写, 占用带宽小,如:{“age”: “12”, “name”: “back”}
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);
代码解读(一行代码系列)
[].forEach.call($$("*"), function(a) {a.style.outline = "1px solid #" + (~~(Math.random() * (1 << 24))).toString(16)})
可以解读一下上面代码的意思么
首先把代码格式化一下
[].forEach.call($$("*"),
function(a) {
a.style.outline = "1px solid #" + (~~(Math.random() * (1 << 24))).toString(16)
}
)
代码解读
- 选取页面所有 DOM 元素,$$() 相当于 document.querySelectorAll(),返回的是一个 NodeList 对象数组,现代浏览器几乎都支持
- 循环遍历 DOM 元素,$$(””)
将所有的
DOM元素转化为
NodeList对象,但这并不是一个 JS 数组,所以不能直接使用
$$(””).forEach() 方法来进行迭代,但是我们可以通过 call 或 apply 方法来使用 forEach。[].forEach.call 等价于 Array.prototype.forEach.call,不过前者字节数更少 - 给元素添加 outline 样式,为什么不使用 border 而是使用 outline 的原因在于:border 在 CSS 盒子模型之内,会影响页面的整体布局,而 outline 在 CSS 盒子模型之外,不会影响到页面的布局
- 生成随机颜色函数
(~~(Math.random() * (1 << 24))).toString(16)
随机颜色区间
- 最小:000000,转为十进制为 0
- 最大:ffffff,转为十进制为 256 * 256 * 256 = 16777216 = (1 << 24)
Math.random() 返回 0~1 的浮点数,Math.random() * (1 << 24) 返回的的即是 (0, 16777216) 之间的浮点数,使用 ~~ 去除浮点数的小数部分,再通过 toString(16) 就转化为十六进制的颜色值了
具体可以参考 传送门
JS 延迟加载的方式有哪些?
defer 和 async、动态创建 DOM 方式(用得最多)、按需异步载入 JS
Ajax 是什么? 如何创建一个 Ajax?
Ajax 是什么
- Ajax 的全称:Asynchronous Javascript And XML
- 异步传输 + JS + xml
- 所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验
如何创建一个 Ajax
- 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
- 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
- 设置响应 HTTP 请求状态变化的函数
- 发送 HTTP 请求
- 获取异步调用返回的数据
- 使用 JavaScript 和 DOM 实现局部刷新
Ajax 解决浏览器缓存问题?
- 在 Ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”, “0”)
- 在 Ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“Cache-Control”, “no-cache”)
- 在 URL 后面加上一个随机数: “fresh = ” + Math.random();
- 在 URL 后面加上时间戳:”nowtime = “ + new Date().getTime();
- 如果是使用 JQuery,直接这样就可以了 $.ajaxSetup({cache: false})。这样页面的所有 Ajax 都会执行这条语句就是不需要保存缓存记录
同步和异步的区别?
同步的概念应该是来自于 OS 中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式)。同步强调的是顺序性,谁先谁后;异步则不存在这种顺序性
同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
如何解决跨域问题?
jsonp、 iframe、window.name、window.postMessage 服务器上设置代理页面
服务器代理转发时,该如何处理 cookie?
可以使用 nginx
具体可以参考 传送门
模块化开发怎么做?
立即执行函数,不暴露私有成员
立即执行函数可以参考 传送门”)
var module1 = (function() {
var _count = 0;
var m1 = function() {
//...
};
var m2 = function() {
//...
};
return {
m1: m1,
m2: m2
};
})();
AMD(Modules/Asynchronous-Definition)、CMD(Common Module Definition)规范区别?
AMD 规范在这里 传送门
CMD 规范在这里 传送门
Asynchronous Module Definition,异步模块定义,所有的模块将被异步加载,模块加载不影响后面语句运行。所有依赖某些模块的语句均放置在回调函数中
区别:
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible
- CMD 推崇依赖就近,AMD 推崇依赖前置
看下面的🌰
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
// ...
})
异步加载 JS 的方式有哪些?
- defer,只支持 IE
- async
- 创建 script,插入到 DOM 中,加载完毕后 callBack
documen.write 和 innerHTML 的区别
- document.write 只能重绘整个页面
- innerHTML 可以重绘页面的一部分
DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?
- 创建新节点
js createDocumentFragment() // 创建一个 DOM 片段 createElement() // 创建一个具体的元素 createTextNode() // 创建一个文本节点
- 添加、移除、替换、插入
js appendChild() removeChild() replaceChild() insertBefore() // 在已有的子节点前插入一个新的子节点
- 查找
js getElementsByTagName() // 通过标签名称 getElementsByName() // 通过元素的 Name 属性的值(IE 容错能力较强,会得到一个数组,其中包括 id 等于 name 值的) getElementById() // 通过元素 id,唯一性
.call() 和 .apply() 的区别?
例子中用 add 来替换 sub,add.call(sub, 3, 1) == add(3, 1),所以运行结果为:console.log(4);
注意:js 中的函数其实是对象,函数名是对 Function 对象的引用
function add(a, b) {
console.log(a + b);
}
function sub(a, b) {
console.log(a - b);
}
add.call(sub, 3, 1);
.call() 参数从第二位开始,可以传入多个参数;.apply() 只能传两个参数,第二个参数只能是一个数组。除此之外,二者没有本质区别
JQuery.extend 与 JQuery.fn.extend 的区别?
- jquery.extend 为 jquery 类添加类方法,可以理解为添加静态方法
- jquery.fn.extend:
源码中 jquery.fn = jquery.prototype,所以对 jquery.fn 的扩展,就是为 jquery 类添加成员函数
使用如下所示
jquery.extend 扩展,需要通过 jquery 类来调用,而 jquery.fn.extend 扩展,所有 jquery 实例都可以直接调用
JQuery 与 jQuery UI 有啥区别?
JQuery 是一个 JS 库,主要提供的功能是选择器,属性修改和事件绑定等等
JQuery UI 则是在 JQuery 的基础上,利用 JQuery 的扩展性,设计的插件。提供了一些常用的界面元素,诸如对话框、拖动行为、改变大小行为等等
JQuery 中如何将数组转化为 json 字符串,然后再转化回来?
JQuery 中没有提供这个功能,所以你需要先编写两个 JQuery 的扩展:
使用 JQuery.fn 进行扩展,这样所有的 JQuery 实例都能调用到
$.fn.stringifyArray = function(array) {
return JSON.stringify(array)
}
$.fn.parseArray = function(array) {
return JSON.parse(array)
}
// 然后调用
$(" ").stringifyArray(array)
针对 JQuery 的优化方法?
- 基于 Class 的选择性的性能相对于 id 选择器开销很大,因为需遍历所有 DOM 元素
- 频繁操作的 DOM,先缓存起来再操作。用 JQuery 的链式调用更好
// 比如
var str = $("a").attr("href");
- 循环优化
for (var i = size; i < arr.length; i++) { ... }
for 循环每一次循环都查找了数组 (arr) 的 length 属性,在开始循环的时候设置一个变量来存储这个数字,可以让循环跑得更快
for (var i = size, length = arr.length; i < length; i++) { ... }
如何判断当前脚本运行在浏览器还是 node 环境中?(阿里)
this === window ? 'browser' : 'node';
通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中
JQuery 的 slideUp 动画 ,如果目标元素是被外部事件驱动, 当鼠标快速地连续触发外部元素事件, 动画会滞后的反复执行,该如何处理呢?
使用 jquery stop()
例如:
$("#div").stop().animate({width: "100px"}, 100);
那些操作会造成内存泄漏?
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收
setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
JQuery一个对象可以同时绑定多个事件,这是如何实现的?
- 多个事件同一个函数
$("div").on("click mouseover", function(){ ... });
- 多个事件不同函数
$("div").on({
click: function(){ ... },
mouseover: function(){ ... }
});
知道什么是 webkit 么? 知道怎么用浏览器的各种工具来调试和 debug 代码么?
Chrome, Safari 浏览器内核是 webkit
用 JS 实现千位分隔符?
使用 正则 + replace 实现
function commafy(num) {
return num && num
.toString()
.replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
return $1 + ",";
});
}
console.log(commafy(1234567.90)); // 1,234,567.90
具体可以参考 传送门
检测浏览器版本版本有哪些方式?
功能检测、userAgent 特征检测
比如
console.log(navigator.userAgent)
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36
What is a Polyfill?
这里再看一下
polyfill 是”在旧版浏览器上复制标准 API 的 JavaScript 补充”,可以动态地加载 JavaScript 代码或库,在不支持这些标准 API 的浏览器中模拟它们。例如,geolocation(地理位置)polyfill 可以在 navigator 对象上添加全局的 geolocation 对象,还能添加 getCurrentPosition 函数以及“坐标”回调对象,所有这些都是 W3C 地理位置 API 定义的对象和函数。因为 polyfill 模拟标准 API,所以能够以一种面向所有浏览器未来的方式针对这些 API 进行开发,一旦对这些 API 的支持变成绝对大多数,则可以方便地去掉 polyfill,无需做任何额外工作
做的项目中,有没有用过或自己实现一些 polyfill 方案(兼容性处理方案)?
这里再看一下
比如:html5shiv、Geolocation、Placeholder
使用 JS 实现获取文件扩展名?
function getFileExtension(filename) {
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}
String.lastIndexOf() 方法返回指定值(本例中的’.‘)在调用该方法的字符串中最后出现的位置,如果没找到则返回 -1
对于 filename 和 .hiddenfile,lastIndexOf 的返回值分别 为 0 和 -1 无符号右移操作符(>>>)将 -1 转换为 4294967295,将 -2 转换为 4294967294,这个方法可以保证边缘情况时文件名不变
String.prototype.slice() 从上面计算的索引处提取文件的扩展名。如果索引比文件名的长度大,结果为 “”
Webpack 热更新实现原理?
- Webpack 编译期,为需要热更新的 entry 注入热更新代码(EventSource 通信)
- 页面首次打开后,服务端与客户端通过 EventSource 建立通信渠道,把下一次的 hash 返回前端
- 客户端获取到 hash,这个 hash 将作为下一次请求服务端 hot-update.js 和 hot-update.json 的 hash
- 修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
- 客户端获取到 hash,成功后客户端构造 hot-update.js script 链接,然后插入主文档
- hot-update.js 插入成功后,执行 hotAPI 的 createRecord 和 reload 方法,获取到 Vue 组件的 render 方法,重新 render 组件,继而实现 UI 无刷新更新
Object.is() 与原来的比较操作符 ===、== 的区别?
两等号判等,会在比较时进行类型转换
三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回 false)
Object.is 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,Object.is(NaN, NaN) 会返回 true
Object.is 应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格
一些问题,持续更新答案
用原生 JavaScript 的实现过什么功能吗?
可以用原生 JS 实现一个跨平台、兼容各个浏览器的绑定鼠标单击事件
可以看下面那个问题的🌰
写一个通用的事件侦听器函数
看下面的🌰
var EventUtil = {
// 根据情况分别使用 dom2 || IE || dom0 方式 来添加事件
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
// 根据情况分别获取 DOM 或者 IE 中的事件对象,事件目标,阻止事件的默认行为
getEvent: function(event) {
return event ? event: window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
// 根据情况分别使用 dom2 || IE || dom0 方式来删除事件
removeHandler: function(element,type,handler){
if (element.removeHandler) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
// 根据情况分别取消 DOM 或者 IE 中事件冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
var btn = document.getElementById("myBtn"),
handler = function() {
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);
具体可以参考 传送门
页面编码和被请求的资源编码如果不一致如何处理?
比如:http://www.yyy.com/a.html
中嵌入了一个 http://www.xxx.com/test.js
a.html 的编码是 gbk 或 gb2312 的。而引入的 js 编码为 utf-8,那就需要在引入的时候
<script src="http://www.xxx.com/test.js" charset="utf-8"></script>
同理,如果你的页面是 utf-8 的,引入的 js 是 gbk 的,那么就需要加上 charset=“gbk”
另有一种说法
对于 ajax 请求传递的参数,如果是 get 请求方式,参数如果传递中文,在有些浏览器(例如 IE 浏览器和其他浏览器的 IE 兼容模式)会乱码,不同的浏览器对参数编码的处理方式不同,所以对于 get 请求的参数需要使用 encodeURIComponent
函数对参数进行编码处理,后台开发语言都有相应的解码 api。对于 post 请求不需要 进行编码
requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何缓存的?)
概念
requireJS 是基于 AMD 模块加载规范,使用回调函数来解决模块加载的问题
原理
requireJS 是使用创建 script 元素(标签),通过指定 script 元素的 src 属性来实现加载模块的
特点
- 实现 js 文件的异步加载,避免网页失去响应
- 管理模块之间的依赖,便于代码的编写和维护
项目优化
r.js 是基于 requireJS 模块化的基础上进一步的压缩和打包成一个 js,请求数大大减少,便于优化
另一种说法
核心是 js 的加载模块,通过正则匹配模块以及模块的依赖关系,保证文件加载的先后顺序,根据文件的路径对加载过的文件做了缓存
JS 模块加载器的轮子怎么造,也就是如何实现一个模块加载器?
目前模块化的思想分为 CommonJS、AMD 和 CMD
模块化的核心思想:
- 拆分。将 js 代码按功能逻辑拆分成多个可复用的 js 代码文件(模块)
- 加载。如何将模块进行加载执行和输出
- 注入。能够将一个 js 模块的输出注入到另一个 js 模块中
- 依赖管理。前端工程模块数量众多,需要来管理模块之间的依赖关系
一个模块的加载可能存在以下几种可能的状态
- 加载(load)状态,包括未加载(preload)状态、加载(loading)状态和加载完毕(loaded)状态
- 正在加载依赖(pending)状态
- 模块回调完成(finished)状态
因此,需要为每个加载的模块加上状态标志(status),来识别目前模块的状态
具体可以参考 传送门
谈一谈你对 ECMAScript6 的了解?
具体可以参考 传送门
ECMAScript6 怎么写 class,为什么会出现 class 这种东西?
在没有 ES6 带来的 class 的时候,在编写 JavaScript 的时候很多时候会通过构造函数和原型链来添加方法属性,实现 class 的功能
看下面的🌰
// Box 是一个构造器
function Box(color) {
this.type = 'circle';
this.color = color;
}
// 可以通过 prototype 的方式来加一条实例方法
Person.prototype.hello = function() {
console.log('hello ' + this.color);
}
// 对于私有属性(Static method),不能放在原型链上。可以直接放在构造函数上面
Person.fn = function() {
console.log('static');
};
// 通过 new 来创建
var circle = new Box('red');
但是在 ES6 的规范中,可以使用 class 语法,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的 class 改写,就是下面的🌰
class Box {
constructor(color) {
this.color = color;
this.type = "corcle"
}
hello() {
console.log('hello ' + this.name);
}
static fn() {
console.log('static');
};
}
上面代码定义了一个”类”,可以看到里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象
需要注意 - class 内的方法不需要 function 关键字,直接把函数定义放进去了就可以了 - 另外,方法之间不需要逗号分隔,加了会报错 - class 内部默认是严格模式
需要注意这个和 JavaScript 中的对象写法是不一样的。而是看成是构造函数的写法。而且目前使用 typeof 来判断 class 的类型的时候返回的结果是 function
constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加
class Box { ... }
// 等同于
class Box {
constructor() {}
}
数组和对象有哪些原生方法,列举一下?
数组
- forEach 遍历所有元素
- every 判断所有元素是否都符合条件
- some 判断是否有至少一个元素符合条件
- sort 排序
- map 对元素重新组装,生成新数组
- filter 过滤符合条件的元素
对象
- for-in 遍历对象属性
具体可以参考 传送门
JS 怎么实现一个类。怎么实例化这个类?
大体上分为三种方法
- 构造函数法
- Object.create() 法
- 极简主义法
具体可以参考 传送门
JavaScript 中的作用域与变量声明提升?
具体可以查看 github 传送门
如何编写高性能的 Javascript?
编写高性能 Javascript 需要注意的点还是挺多的,也有很多小技巧。下面只是列举出了一些写的不错的博文,更多的内容可以继续查找,或者在平时的工作学习中进行积累
那些操作会造成内存泄漏?
什么是内存泄漏
内存泄漏是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。在 C++ 中,因为是手动管理内存,内存泄漏是经常出现的事情。而现在流行的 C# 和 Java 等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄漏。浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有 bug,因此会产生内存泄漏
首先要了解 JS 的垃圾回收机制,然后再了解可能产生内存泄漏的操作
JS 的垃圾回收机制 - GC
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。JavaScript 垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数
- 标记清除
JS 中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为 “进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为 “离开环境”,看下面的🌰
function test() {
var a = 10; // 被标记,进入环境
var b = 10; // 被标记,进入环境
}
test(); // 执行结束后,a 和 b 被标记为离开环境,进入回收
- 引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值(function object array)赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 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
}
JQuery 的源码看过吗?能不能简单概况一下它的实现原理?
(function(window, undefined) { ... })(window);
- jQuery 利用 JS 函数作用域的特性,采用立即调用表达式包裹了自身,解决命名空间和变量污染问题
window.jQuery = window.$ = jQuery;
- 在闭包当中将 jQuery 和 $ 绑定到 window 上,从而将 jQuery 和 $ 暴露为全局变量
JQuery.fn 的 init 方法返回的 this 指的是什么对象?为什么要返回 this?
- jQuery.fn 的 init 方法 返回的 this 就是 jQuery 对象
- 用户使用 jQuery() 或 $() 即可初始化 jQuery 对象,不需要动态的去调用 init 方法
JQuery 中如何将数组转化为 JSON 字符串,然后再转化回来?
实现原理是拓展 jQuery 的方法,拓展的方法中使用 JSON 实现需求
看下面的🌰
// 通过原生 JSON.stringify/JSON.parse 扩展 jQuery 实现
$.array2json = function(array) {
return JSON.stringify(array);
}
$.json2array = function(array) {
// $.parseJSON(array); // 3.0 开始,已过时
return JSON.parse(array);
}
// 调用
var json = $.array2json(['a', 'b', 'c']);
var array = $.json2array(json);
JQuery 的属性拷贝(extend)的实现原理是什么,如何实现深拷贝?
- 浅拷贝(只复制一份原始对象的引用)
var newObject = $.extend({}, oldObject);
- 深拷贝(对原始对象属性所引用的对象进行进行递归拷贝)
var newObject = $.extend(true, {}, oldObject);
JQuery 的队列是如何实现的?队列可以用在哪些地方?
jQuery 核心中有一组队列控制方法,由 queue()/dequeue()/clearQueue() 三个方法组成
主要应用于 animate(),ajax 以及其他要按时间顺序执行的事件中
看下面的🌰
var func1 = function() {alert('事件 1');}
var func2 = function() {alert('事件 2');}
var func3 = function() {alert('事件 3');}
var func4 = function() {alert('事件 4');}
// 入栈队列事件
$('#box').queue("queue1", func1); // push func1 to queue1
$('#box').queue("queue1", func2); // push func2 to queue1
// 替换队列事件
$('#box').queue("queue1", []); // delete queue1 with empty array
$('#box').queue("queue1", [func3, func4]); // replace queue1
// 获取队列事件(返回一个函数数组)
$('#box').queue("queue1"); // [func3(), func4()]
// 出栈队列事件并执行
// 按顺序出栈
$('#box').dequeue("queue1"); // return func3 and do func3
$('#box').dequeue("queue1"); // return func4 and do func4
// 清空整个队列
$('#box').clearQueue("queue1"); // delete queue1 with clearQueue
谈一下 JQuery 中的 bind(),live(),delegate(),on() 的区别?
- bind 直接绑定在目标元素上
- live 通过冒泡传播事件,默认响应在 document 上,支持动态数据
- delegate 更精确的小范围使用事件代理,性能优于 live
- on 是最新的 1.9 版本整合了之前的三种方式的新事件绑定机制
JQuery 一个对象可以同时绑定多个事件,这是如何实现的?
看下面的🌰
// 绑定 mouseover 和 mouseout 事件
$("#btn").on("mouseover mouseout", func);
// 绑定 mouseover, mouseout 和 click 事件
$("#btn").on({
mouseover: func1,
mouseout: func2,
click: func3
});
是否知道自定义事件。JQuery 里的 fire 函数是什么意思,什么时候用?
- 事件即 “发布/订阅” 模式,自定义事件即 “消息发布”,事件的监听即 “订阅订阅”
- JS 原生支持自定义事件,看下面的🌰
document.createEvent(type); // 创建事件
event.initEvent(eventType, canBubble, prevent); // 初始化事件
target.addEventListener('dataavailable', handler, false); // 监听事件
target.dispatchEvent(e); // 触发事件
- jQuery 里的 fire 函数用于调用 jQuery 自定义事件列表中的事件
JQuery 是通过哪个方法和 Sizzle 选择器结合的?(jQuery.fn.find() 进入 Sizzle)
- Sizzle 选择器采取 Right To Left 的匹配模式,先搜寻所有匹配标签,再判断它的父节点
- jQuery 通过
$(selecter).find(selecter);
和 Sizzle 选择器结合
具体可以参考 传送门
针对 JQuery 性能的优化方法?
- 缓存频繁操作 DOM 对象
- 尽量使用 id 选择器代替 class 选择器
- 总是从 #id 选择器来继承
- 尽量使用链式操作
- 使用时间委托 on 绑定事件
- 采用 jQuery 的内部函数 data() 来存储数据
- 使用最新版本的 jQuery
JQuery 和 Zepto 的区别?各自的使用场景?
- jQuery 主要目标是 PC 的网页中,兼容全部主流浏览器。在移动设备方面,单独推出 jQuery Mobile
- Zepto 从一开始就定位移动设备,相对更轻量级。它的 API 基本兼容 jQuery,但对 PC 浏览器兼容不理想
Zepto 的点透问题如何解决?
首先了解一下什么是 Zepto 点透
你可能碰到过在列表页面上创建一个弹出层,弹出层有个关闭的按钮,你点了这个按钮关闭弹出层后后,这个按钮正下方的内容也会执行点击事件(或打开链接)。这个被定义为这是一个 “点透” 现象
例如在点击弹出来的选择组件的右上角完成后会让完成后面的 input 输入框聚焦,弹出输入键盘,也就是点透了
点透产生的原因
zepto 的 tap 通过兼听绑定在 document 上的 touch 事件来完成 tap 事件的模拟的,既 tap 事件是冒泡到 document 上触发的
再点击完成时的 tap 事件(touchstart/touchend)需要冒泡到 document 上才会触发,而在冒泡到 document 之前,用户手的接触屏幕(touchstart)和离开屏幕(touchend)是会触发 click 事件的,因为 click 事件有延迟触发(这就是为什么移动端不用 click 而用 tap 的原因。大概是 300ms,为了实现 safari 的双击事件的设计),所以在执行完 tap 事件之后,弹出来的选择组件马上就隐藏了,此时 click 事件还在延迟的 300ms 之中,当 300ms 到来的时候,click 到的其实不是完成而是隐藏之后的下方的元素,如果正下方的元素绑定的有 click 事件此时便会触发,如果没有绑定 click 事件的话就当没 click,但是正下方的是 input 输入框(或者 select 选择框或者单选复选框),点击默认聚焦而弹出输入键盘,也就出现了上面的点透现象
点透的解决方法(三种)
- github 上有个 fastclick 可以完美解决 传送门
引入 fastclick.js,因为 fastclick 源码不依赖其他库所以你可以在原生的 js 前直接加上
window.addEventListener('load', function() {
FastClick.attach( document.body );
}, false );
或者在 zepto 或者 jqm 的 js 里面加上
$(function() {
FastClick.attach(document.body);
});
require 的话就这样
var FastClick = require('fastclick');
FastClick.attach(document.body, options);
- 用 touchend 代替 tap 事件并阻止掉 touchend 的默认行为 preventDefault()
$('#cbFinish').on('touchend', function(e) {
// 很多处理比如隐藏什么的
e.preventDefault();
});
- 延迟一定的时间(300ms+)来处理事件
$("#cbFinish").on("tap", function (event) {
setTimeout(function() {
// 很多处理比如隐藏什么的
}, 320);
});
这种方法其实很好,可以和 fadeInIn/fadeOut 等动画结合使用,可以做出过度效果
理论上上面的方法可以完美的解决 tap 的点透问题,如果真的倔强到不行,用 click
具体可以参考 传送门
JQuery UI 如何自定义组件?
- 通过向 $.widget() 传递组件名称和一个原型对象来完成
$.widget('ns.widgetName', [baseWidget], widgetPrototype);
你觉得 jQuery 或 zepto 源码有哪些写的好的地方
- jquery 源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入 window 对象参数,可以使 window 对象作为局部变量使用,好处是当 jquery 中访问 window 对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问 window 对象。同样,传入 undefined 参数,可以缩短查找 undefined 时的作用域链
(function(window, undefined) {
// 用一个函数域包起来,就是所谓的沙箱
// 在这里边 var 定义的变量,属于这个函数域内的局部变量,避免污染全局
// 把当前沙箱需要的外部变量通过函数参数引入进来
// 只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数
window.jQuery = window.$ = jQuery;
})(window);
- jquery 将一些原型属性和方法封装在了 jquery.prototype 中,为了缩短名称,又赋值给了 jquery.fn,这是很形象的写法
- 有一些数组或对象的方法经常能使用到,jQuery 将其保存为局部变量以提高访问速度
- jquery 实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率
需求:实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案?
用 cookie 或者 localStorage 来记录应用的状态即可,刷新页面时读取一下这个状态,然后发送相应 ajax 请求来改变页面即可
HTML5 里引用了新的 API,就是 history.pushState 和 history.replaceState,就是通过这个接口做到无刷新改变页面 URL 的
虽然 ajax 可以无刷新改变页面内容,但无法改变页面 URL
其次为了更好的可访问性,内容发生改变后,改变 URL 的 hash。但是 hash 的方式不能很好的处理浏览器的前进、后退等问题
有的浏览器引入了 onhashchange 的接口,不支持的浏览器只能定时去判断 hash 是否改变
再有,ajax 的使用对搜索引擎很不友好,往往蜘蛛爬到的区域是空的
为了解决传统 ajax 带来的问题,HTML5 里引入了新的 API,即:history.pushState, history.replaceState
可以通过 pushState 和 replaceState 接口操作浏览器历史,并且改变当前页面的 URL
pushState 是将指定的 URL 添加到浏览器历史里,replaceState 是将指定的 URL 替换当前的 URL
如何调用,看下面的🌰
var state = {title: title, url: options.url, otherkey: othervalue};
window.history.pushState(state, document.title, url);
state 对象除了要添加 title 和 url 之外,也可以添加其他的数据,比如:还想将一些发送 ajax 的配置给保存起来
replaceState 和 pushState 是相似的,不需要多做解释
如何响应浏览器的前进、后退操作
window 对象上提供了 onpopstate 事件,上面传递的 state 对象会成为 event 的子对象,这样就可以拿到存储的 title 和 URL 了
看下面的🌰
window.addEventListener('popstate', function(e) {
if (history.state) {
var state = e.state; // do something(state.url, state.title);
}
}, false);
这样就可以结合 ajax 和 pushState 完美的进行无刷新浏览了
具体可以参考 传送门
移动端最小触控区域是多大?
因为达成了一个基本共识,所以就谈论的就少了
苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,也可以查看 传送门
把 Script 标签 放在页面的最底部的 body 封闭之前 和封闭之后有什么区别?浏览器会如何解析它们?
html 标签只包含 head 和 body 两个标签,解析时,所有标签都会解析进这两个标签里边。body 之前的任何位置都会解析进 head 里边,之后的都会解析进 body 里边
- 将 JavaScript 标识放置
<head></head>
在头部之间,使之在主页和其余部分代码之前预先装载,从而可使代码的功能更强大; 比如对 *.js 文件的提前调用。 也就是说把代码放在<head>
区在页面载入的时候,就同时载入了代码,在<body>
区调用渲染 html 模板时就不需要再载入代码了,速度就提高了,这种区别在小程序上是看不出的,当运行很大很复杂的程序时,就可以看出了。当然也可以将 JavaScript 标识放置在<body></body>
主体之间以实现某些部分动态地创建文档。这里比如制作鼠标跟随事件,肯定只有当页面加载后再进行对鼠标坐标的计算。或者是 filter 滤镜与 javascript 的联合使用产生的图片淡入淡出效果 - 放入 html 的 head,是页面加载前就运行,放入 body 中,则加载后才运行 javascript 的代码
- 所以 head 里面的先执行
移动端的点击事件的有延迟,时间是多久,为什么会有?怎么解决这个延时?
click 有 300ms 延迟,为了实现 safari 的双击事件的设计
浏览器要知道你是不是要双击操作
解决方法可以查看 “Zepto 的点透问题如何解决?” 问题的解决方案
知道各种 JS 框架(Angular, Backbone, Ember, React, Meteor, Knockout…)么? 能讲出他们各自的优点和缺点么?
- Backbone.js 和 Spine.js 很相近,核心差不多,只是 API 不同。框架很小,没有 DOM Binding - 需要自己写。Backbone.js 应用广泛,也比较成熟,社区比较大。
- Knockout.js 则是几乎纯粹的 DOM Binding,没有一个默认的组织程序的架构
- Ember.js Batman.js Angular.js 都比较晚一些,吸取了前两类的优点,既提供 DOM Binding,又有 MVC 架构支持。具体的不同在于 DOM Binding 的机制,看个人喜好
TODO
Underscore 对哪些 JS 原生对象进行了扩展以及提供了哪些好用的函数方法?
TODO
解释 JavaScript 中的作用域与变量声明提升?
具体可以查看 github 传送门
Node.js 的适用场景?
Node.js 特点
- 它是一个 Javascript 运行环境
- 依赖于 Chrome V8 引擎进行代码解释
- 事件驱动
- 非阻塞 I/O
- 轻量、可伸缩,适于实时数据交互应用
- 单进程,单线程
NodeJS 带来的对系统瓶颈的解决方案
- 并发连接
- I/O 阻塞
NodeJS 的优缺点
优点:
- 高并发(最重要的优点)
- 适合 I/O 密集型应用
缺点:
- 不适合 CPU 密集型应用;CPU 密集型应用给 Node 带来的挑战主要是:由于 JavaScript 单线程的原因,如果有长时间运行的计算(比如大循环),将会导致 CPU 时间片不能释放,使得后续 I/O 无法发起;解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞 I/O 调用的发起
- 只支持单核 CPU,不能充分利用 CPU
- 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃。原因:单进程,单线程。解决方案:(1)Nnigx 反向代理,负载均衡,开多个进程,绑定多个端口;(2)开多个进程监听同一个端口,使用 cluster 模块
- 开源组件库质量参差不齐,更新快,向下不兼容
- Debug 不方便,错误没有 stack trace
适合 NodeJS 的场景
- RESTful API
- 统一 Web 应用的 UI 层
- 大量 Ajax 请求的应用
总而言之,NodeJS 适合运用在高并发、I/O 密集、少量业务逻辑的场景
具体可以查看 传送门
(如果会用 node)知道 route, middleware, cluster, nodemon, pm2, server-side rendering 么?
TODO
解释一下 Backbone 的 MVC 实现方式?
什么是 Backbone?
Backbone.js 是十大 JS 框架之首,Backbone.js 是一个重量级 js MVC 应用框架,也是 js MVC 框架的鼻祖。它通过 Models 数据模型进行键值绑定及 custom 事件处理,通过模型集合器 Collections 提供一套丰富的 API 用于枚举功能,通过视图 Views 来进行事件处理及与现有的 Application 通过 JSON 接口进行交互
简而言之,Backbone 是实现了 web 前端 MVC 模式的 js 库
什么是 MVC?
MVC - Model View Controller
MVC:后端服务器首先通过浏览器获取页面地址,对网址进行解析,得到视图 View 给它的一个网址,然后通过控制器 Controller 进行解析,然后去找对应的数据,找到数据后,再将数据 Model 返回给控制器,控制器 Controller 再对数据进行加工,最后返回给视图,即更新视图 View。这种结构在后端是非常清晰且易实现的
Backbone 中 MVC 的机制
Backbone 将数据呈现为模型,你可以创建模型、对模型进行验证和销毁,甚至将它保存到服务器。当 UI 的变化引起模型属性改变时,模型会触发 “change” 事件;所有显示模型数据的视图会接收到该事件的通知,继而视图重新渲染。无需查找 DOM 来搜索指定 id 的元素去手动更新 HTML。 — 当模型改变了,视图便会自动变化
具体可以查看 传送门
什么是”前端路由”?什么时候适合使用”前端路由”?”前端路由”有哪些优点和缺点?
什么是前端路由?
- 路由是根据不同的 url 地址展示不同的内容或页面
- 前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,之前是通过服务端根据 url 的不同返回不同的页面实现的
什么时候使用前端路由?
- 在单页面应用,大部分页面结构不变,只改变部分内容的使用
前端路由有什么优点和缺点?
优点:
- 用户体验好,不需要每次都从服务器全部获取,快速展现给用户
缺点:
- 使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存
- 单页面无法记住之前滚动的位置,无法在前进,后退的时候记住滚动的位置
如何测试前端代码么? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit …)?
首先要了解什么是测试,测试就是检测你的应用代码是否按预期执行效果
测试方法:单元测试、验收测试、集成测试、端到端测试、组件测试和服务测试
- BDD(Behavior Driven Development):行为驱动开发是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA 和非技术人员或商业参与者之间的协作
- TDD(Test-Driven Development 或者 Test-Driven Design):一种特定的测试方法,先写测试,然后用测试来驱动产品的设计和实现
- Unit Test 单元测试,利用机器去测试,从而代替人手工去测试和调试。可以结合 mock 数据使用,简单方便不拖泥带水
ts 和 tsx 文件可以使用 jest 来写单元测试。*.ts 文件对应的测试文件是 .spec.ts,.tsx 文件对应的测试文件是 *.spec.tsx(使用 snapshot 进行测试)
前端 templating(Mustache, underscore, handlebars)是干嘛的, 怎么用?
TODO
简述一下 Handlebars 的基本用法?
Handlebars.js 是一个非常流行的功能强大的模板引擎,简单易用,具备较好的学习社区。它基于 Mustache 模板引擎,并且做了诸多改进。利用 Handlebars 可以方便的把 html 从 javascript 代码中分离出来,从而书写更清晰的代码
具体可以查看 传送门
简述一下 Handlerbars 的对模板的基本处理流程, 如何编译的?如何缓存的?
TODO
我们给一个 dom 同时绑定两个点击事件,一个用捕获,一个用冒泡。会执行几次事件,会先执行冒泡还是捕获?
所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件
具体可以查看 传送门
请介绍一下 JS 事件节流?
问题隐患
- 一些短时间内频繁触发的事件会导致占用过多的资源,这时候我们设置一个触发间隔
- 频繁触发的事件,例如的 mousemove(鼠标移动)、scroll(滚动条),resize 等……
原理实现
- 初次调用函数时,设置一个定时器,在指定的 间隔 之后运行代码
- 第二次调用函数时(若小于 间隔 时间,定时器 尚未执行),清除定时器并重设一个
- 如果定时器已经执行(过了间隔时间),此次操作就无意义
- 目的是只有在执行函数的请求停止了一段时间(间隔时间)之后才执行
什么是 JS 的函数防抖?
函数节流和函数防抖,两者都是优化高频率执行 js 代码的一种手段
大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片。由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的。反而形成一种视觉效果,就是一张图。就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样
同理,可以类推到 js 代码。在一定时间内,代码执行的次数不一定要非常多。达到一定频率就足够了。因为跑得越多,带来的效果也是一样。倒不如,把 js 代码的执行次数控制在合理的范围。既能节省浏览器 CPU 资源,又能让页面浏览更加顺畅,不会因为 js 的执行而发生卡顿。这就是函数节流和函数防抖要做的事
- 函数节流是指一定时间内 js 方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释
- 函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车
具体可以参考 传送门
ES6 是如何实现编译成 ES5 的?
可以使用 Babel 来完成
具体可以查看 传送门
css-loader 的原理?
webpack 的 loaders 是一块很重要的组成部分。我们都知道 webpack 是用于资源打包的,里面的所有资源都是 “模块”,内部实现了对模块资源进行加载的机制。但是 webpack 本身只能处理 js 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果,例如可以使用 loader 加载器可以快速编译预处理器(less, sass, coffeeScript)。loader 可以在 require() 引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用
loader 的特性是:
- loaders 可以串联,他们应用于管道资源,最后的 loader 将返回 javascript,其它的可返回任意格式(传递给下一个 loader)
- loaders 可以同步也可以异步
- loaders 在 nodejs 下运行并且可以做一切可能的事 loader 接受参数,可用于配置里
- loaders 可以绑定到 extension / RegExps 配置
- loaders 可以通过 npm 发布和安装
- loaders 除了可以访问配置,插件可以给 loaders 提供更多的特性
- loaders 可以释放任意额外的文件