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

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

目录

1.事件代理

2.请解释JavaScript 中this 是如何工作的

3.javascript继承

3.1 原型链继承

3.2 构造函数继承

3.3 组合继承(构造函数继承和原型链继承的结合)

3.4 寄生组合继承(目前最优)

3.5 ES6继承

3.6 ES6与ES5继承的区别

4.为何Object instanceof Function和Function instanceof Object都返回true?

5.javascript模块化

5.1 AMD

5.2 CommonJS

5.3 CMD

5.4 ES6模块化

5.5 对比CommonJS和ES6模块化

6.IIFE 立即执行函数

7.null undefined区别

8.假值

9.隐形数据类型转换

10.== 和 === 有什么不同


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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值