总纲:前端面试知识点大全
目录
4.为何Object instanceof Function和Function instanceof Object都返回true?
1.事件代理
JS事件执行分为三个阶段:捕获阶段、处在目标阶段、冒泡阶段
事件代理就是等事件触发时在执行回调函数,w3c使用addEventListener和removeEventListener,IE使用attachEvent和detachEvent;w3c监听器的第三个参数是是否捕获阶段,默认false,即冒泡阶段执行,IE的监听器没有第三个参数。还有一种是事件委托,在父元素上绑定事件,然后等子元素触发事件,冒泡至父元素时,父元素在执行。主要通过event.target.nodeName进行标签判断子元素。
阻止冒泡 event.stopPropagation()和event.cancelBubble=true
阻止默认行为 event.preventDefault()和event.returnValue=false
PS:IE是事件冒泡流,网景是事件捕获流。
2.请解释JavaScript 中this 是如何工作的
答:记住一点,this永远绑定在调用它的对象。利用call和apply以及bind可以改变this绑定。
3.javascript继承
JavaScript的继承与多态:https://juejin.im/post/5912753ba22b9d005817524e
3.1 原型链继承
核心:将父类的实例作为子类的原型
SubType.prototype = new SuperType();
此时原型对象的constructor的执行变成了SuperType,因为constructor指向构造函数,所以重新指向子类的构造函数即可,即
Subtype.prototype.constructor = SubType;
优点:
1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例,通过instanceof可以判断
2)父类新增原型方法/属性,子类都可以访问到
3)简单、易于实现
缺点:
1)要想子类新增属性和方法,必须要在new SuperType()这样的语句之后执行,不能放到构造函数中
2)无法实现多继承
3)来自原型对象的引用属性是所有实例共享的
4)创建子类实例时,无法向父类构造函数传参
3.2 构造函数继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。
在子类的构造函数内使用SuperType.call(this);
优点:
1、解决了原型链继承中,子类实例共享父类属性的问题
2、创建子类实例是,可以向父类传递参数
3、可以实现多继承(call多个父类对象)
缺点:
1、实例并不是父类的实例,只是子类的实例(instanceof可以判断)
2、只能继承父类的实例属性和方法,不能继承原型属性/方法
3、无法实现函数服用,每个子类都有父类的实例函数的副本,影响性能
3.3 组合继承(构造函数继承和原型链继承的结合)
核心:在构造函数内利用call方法,直接调用属性,然后在原型prototype上绑定绑定方法(通过实例化new父对象),然后修改constructor指向子对象的构造函数。
function SubType(name){
SuperType.call(this,name);//第二次调用父类
}
SubType.prototype = new SuperType();//第一调用父类
SubType.prototype.constructor = SubType;
优点:
1、既是子类的实例,也是父类的实例
2、不存在引用属性共享问题
缺点:
1、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
3.4 寄生组合继承(目前最优)
核心:通过寄生方式,砍掉父类的实例属性,这样在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免了组合继承的缺点
function SubType(name) {
SuperType.call(this,name);
}
//es6继承更方便
(function () {
var Super = function () {};
Super.prototype = SuperType.prototype;
SubType.prototype = new Super();
SubType.prototype.constructor = SubType;
})();
《JS高程》里面是声明了一个object函数:
function object(prototype) {
function F(){}
F.prototype = prototype
return new F();
}
上面的就能改写成
SubType.prototype = object(SuperType.prototype);
SubType.prototype.constructor = SubType;
后来ES5提出了Object.create(prototype,[propertiesObject])方法,当只有一个参数时,它的行为和上面定义的object方法一样,所以就可以改写成下面的:
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
3.5 ES6继承
核心:通过ES6语法糖class,使用extends关键字
class SubType extends SuperType{
constructor(...args){
super(...args);
}
}
通过在class的constructor方法内调用super,调用父类的constructor方法,用于新建父类的this对象。
SubType.__proto__ === SuperType;//true
SubType.prototype.__proto__ === SuperType.prototype;//true
只要带有prototype属性的函数就能被extends关键字继承。
ES6子类继承父类,必须在constructor函数的第一行调用super(),即调用父类的构造函数,获取this对象,相当于A.prototype.constructor.call(this)。
在子类的构造函数中,必须调用super之后才可以使用this关键字,不然子类本身没有this对象。因为子类实例的构建是基于父类实例加工,只有super方法才能返回父类的实例。
3.6 ES6与ES5继承的区别
ES5的继承实质是先创造子类的实例对象this,然后在将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承实质是先创造父类的实例对象this(所以必须先调用super方法),然后在用子类的构造函数修改this。如果子类没有定义constructor方法,那么会默认添加这个方法。
super在子类中一般有三种作用:
1、作为父类的构造函数调用,就是上面所说的那种方法。super()
2、在普通方法中,当做对象使用,指向父类的原型对象。super.method()
PS:ES6规定,通过super调用父类的方法时,super会绑定子类的this。
3、在静态方法中,作为父类调用,指向父类本身,不是原型对象。
也就是使用父类静态属性和方法。比如 static func(){ super.method() },就是SuperType.method()的意思。
ES6明确规定,Class内部只有静态方法,没有静态属性
4.为何Object instanceof Function和Function instanceof Object都返回true?
Object, Function, Array等等这些都被称作是构造“函数”,他们都是函数。而所有的函数都是构造函数Function的实例。从原型链机制的的角度来说,那就是说所有的函数都能通过原型链找到创建他们的Function构造函数的构造原型Function.protorype对象,所以:
alert(Object instanceof Function);// return true
与此同时,又因为Function.prototype是一个对象,所以他的构造函数是Object. 从原型链机制的的角度来说,那就是说所有的函数都能通过原型链找到创建他们的Object构造函数的构造原型Object.prototype对象,所以:
alert(Function instanceof Object);// return true
有趣的是根据我们通过原型链机制对instanceof进行的分析,我们不难得出一个结论:Function instanceof Function 依然返回true, 原理是一样的:
1. Function是构造函数,所以它是函数对象
2. 函数对象都是由Function构造函数创建而来的,原型链机制解释为:函数对象的原型链中存在Function.prototype
3. instanceof查找原型链中的每一个节点,如果Function.prototype的构造函数Function的原型链中被查到,返回true
5.javascript模块化
早期前端没有模块化的概念,后面出现了传统的模块化方法通过添加全局变量、命名空间方式、闭包封装的形式实现模块化,但是不能解决依赖困难问题。后来出现了commonJS(同步加载)和AMD(异步加载)两种模块化规范,其中AMD主要用于前端开发,流行库为RequireJS,而commonJS用于后台和Node.js,CMD是SeaJS在推广时形成的规范。
5.1 AMD
1、仅仅需要在全局环境下定义require和define,不需要其他的全局变量
2、通过文件路径或模块自己声明的模块名定位模块
3、模块实现声明依赖,依赖的加载与执行均由加载器操作
4、提供了打包工具自动分析依赖并合并
典型用法:
define(function (require) {
//通过相对路径获得依赖模块
const bar = require('./bar');
//模块产出
return function () {
//...
};
});
5.2 CommonJS
相比于AMD的模块格式,CommonJS的模块格式更简洁,而且可以更方便地实现前后端代码共用,因为Node.js就是采用的CommonJS规范
典型用法:
//通过相对路径获得依赖模块
const bar = require('./bar');
//模块产出
module.exports = function () {
//...
};
module.exports = {
//...
};
5.3 CMD
CMD和AMD的区别有以下几点:
1、对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
2、CMD推崇依赖就近,AMD推崇依赖前置。
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。
3、AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。
5.4 ES6模块化
相比于AMD或是CommonJS规范,ES6的模块化语句更清晰易懂,且其引用和暴露的方式更多样。
典型用法:
//通过相对路径获得依赖模块
import {bar, far} from './bar'
//模块产出
export let bar = 3;
export default function () {
//...
}
5.5 对比CommonJS和ES6模块化
1、CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用(可被修改)
2、CommonJS模块是运行时加载,ES6模块是编译时输出接口
第一个差异主要是说,CommonJS的一旦输出,模块内部的变化不会影响到这个值;而JS引擎对脚本静态分析的时候,遇到模块加载命令import就会生成一个只读引用,等到脚本真正执行时,在根据这个只读引用到被加载的模块中取值,即ES6模块是动态引用,且不会缓存值,模块内部导致原始值变了,import加载的值也会跟着变。
第二个差异是因为CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行结束时才会生生。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
第三个差异:模块循环加载(ES标准入门488页)
模块循环加载是,因为commonjs是加载时执行生成一个对象。即无论加载多少次都只会在第一次加载时运行一次,以后加载都是返回第一次运行的结果,除非手动清除系统缓存,所以它相当于时拷贝。当存在循环加载时,已经执行的部分可以输出,未执行的部分就不会输出。所以也不会死循环。
而ES6是动态引用,import的时候只是添加了一个引用标记,等运行时才会去被加载模块取值。所以当a.js import b.js的时候,先执行b取值,然后b又import a.js,然后要去执行a,但此时a已经在执行了,但没执行完,所以取不到输出的值。当b执行完成,a取到值后,a继续执行,直到完成。
6.IIFE 立即执行函数
立即执行函数常与闭包结合使用,形成命名空间,避免全局污染。形如(function () {...})();和(function () {...}());,他两没啥区别,官方推荐后者。
因为JS遇到()会认为是一个表达式,然后后面加上()就是执行函数了。
7.null undefined区别
基础类型Null、Boolean、Undefined、String、Number和Symbol
其中Null只有一个值就是null,Undefined也只有一个值就是undefined,null是同时又是一个object,因为他表示指向空对象的指针,undefined表示未定义,主要用于字面量的判断。
null和undefined转为boolean都是false,和false比较也是真,全等比较为假;
null是一个表示"无"的对象,转为数值时为0;
undefined是一个表示"无"的原始值,转为数值时为NaN;
当声明的变量还未被初始化时,变量的默认值为undefined;
null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象;
undefined表示 “缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
1. 变量被声明了,但没有赋值时,就等于 undefined;
2. 调用函数时,应该提供的参数没有提供,该参数等于 undefined;
3. 对象没有赋值的属性,该属性的值为 undefined;
4. 函数没有返回值时,默认返回 undefined。
null表示“没有对象”,即该处不应该有值。典型用法是:
1. 作为函数的参数,表示该函数的参数不是对象
2. 作为对象原型链的终点
8.假值
学习博客:https://blog.csdn.net/sinat_36521655/article/details/80567943
假值的有6种情况:null、undefined、""(空字符串)、+0、-0、NaN。
[]空数组返回true,所有对象都是返回true,symbol也是返回true
{! + [] + [] + ![]}.length;//9, "truefalse"
9.隐形数据类型转换
学习博客:https://blog.csdn.net/sinat_36521655/article/details/81911329
toBoolean()方法:
如果preferredType为Number,ToPrimitive执行过程如下:
如果obj为原始值,直接返回;
否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
否则调用obj.toString(),如果执行结果是原始值,返回之;
否则抛异常。
如果preferredType为String,将上面的第2步和第3步调换,即:
如果obj为原始值,直接返回;
否则调用obj.toString(),如果执行结果是原始值,返回之;
否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
否则抛异常。
10.== 和 === 有什么不同
==是值比较,===是同时值和类型比较
undefined == null 返回true
0 == null 返回false 0为基础类型 null为对象
两个空对象 == 判断返回false,因为是值判断(自身引用的值,类似于指针)只有他们指向想同一个对象时才返回真。
因为undefined由null派生出来的,同时JS规范文档这么定义的。其余情况是转成valueOf()方法转成值去比较。
1、false、0、''、[]相互“==”判断为true,其余为false
[]空数组在toNumber时转成0
2、undefined、null相互“==”判断为true,其余为false
undefined在toNumber时转换成NaN,所以与其他的不等。只是按照约定undefined == null判断true
3、NaN跟自身都不相等,所以永远判断为false