玖叶教程网

前端编程开发入门

前端经典面试题(60道前端面试题包含JS、CSS、React、网络、)上

由于文章内容过多,平台篇幅限制,需要阅读前文内容的看官们点此:前端经典面试题(60道前端面试题包含JS、CSS、React、网络、浏览器、程序题等)上

文章转载:乐字节

3、JS的四种设计模式

参考答案


工厂模式


简单的工厂模式可以理解为解决多个相似的问题;


function CreatePerson(name,age,sex) {

var obj = new Object();

obj.name = name;

obj.age = age;

obj.sex = sex;

obj.sayName = function(){

return this.name;

}

return obj;

}

var p1 = new CreatePerson("longen",'28','男');

var p2 = new CreatePerson("tugenhua",'27','女');

console.log(p1.name); // longen

console.log(p1.age); // 28

console.log(p1.sex); // 男

console.log(p1.sayName()); // longen


console.log(p2.name); // tugenhua

console.log(p2.age); // 27

console.log(p2.sex); // 女

console.log(p2.sayName()); // tugenhua


单例模式


只能被实例化(构造函数给实例添加属性与方法)一次


// 单体模式

var Singleton = function(name){

this.name = name;

};

Singleton.prototype.getName = function(){

return this.name;

}

// 获取实例对象

var getInstance = (function() {

var instance = null;

return function(name) {

if(!instance) {//相当于一个一次性阀门,只能实例化一次

instance = new Singleton(name);

}

return instance;

}

})();

// 测试单体模式的实例,所以a===b

var a = getInstance("aa");

var b = getInstance("bb");


沙箱模式


将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,否则无法使用里面的值


let sandboxModel=(function(){

function sayName(){};

function sayAge(){};

return{

sayName:sayName,

sayAge:sayAge

}

})()


发布者订阅模式


就例如如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送,


//发布者与订阅模式

var shoeObj = {}; // 定义发布者

shoeObj.list = []; // 缓存列表 存放订阅者回调函数


// 增加订阅者

shoeObj.listen = function(fn) {

shoeObj.list.push(fn); // 订阅消息添加到缓存列表

}


// 发布消息

shoeObj.trigger = function() {

for (var i = 0, fn; fn = this.list[i++];) {

fn.apply(this, arguments);//第一个参数只是改变fn的this,

}

}

// 小红订阅如下消息

shoeObj.listen(function(color, size) {

console.log("颜色是:" + color);

console.log("尺码是:" + size);

});


// 小花订阅如下消息

shoeObj.listen(function(color, size) {

console.log("再次打印颜色是:" + color);

console.log("再次打印尺码是:" + size);

});

shoeObj.trigger("红色", 40);

shoeObj.trigger("黑色", 42);


代码实现逻辑是用数组存贮订阅者, 发布者回调函数里面通知的方式是遍历订阅者数组,并将发布者内容传入订阅者数组


4、列举出集中创建实例的方法

参考答案


1.字面量


let obj={'name':'张三'}


2.Object构造函数创建


let Obj=new Object()

Obj.name='张三'


3.使用工厂模式创建对象


function createPerson(name){

var o = new Object();

o.name = name;

};

return o;

}

var person1 = createPerson('张三');


4.使用构造函数创建对象


function Person(name){

this.name = name;

}

var person1 = new Person('张三');


5、简述一下前端事件流

参考答案


HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。


什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。


事件捕获阶段


处于目标阶段


事件冒泡阶段


addEventListener:addEventListener是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。


IE只支持事件冒泡。


6、Function._proto_(getPrototypeOf)是什么?

参考答案


获取一个对象的原型,在chrome中可以通过__proto__的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。


那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。


Function.__proto__==Object.prototype //false

Function.__proto__==Function.prototype//true


我们发现Function的原型也是Function。


我们用图可以来明确这个关系:



7、简述一下原型 / 构造函数 / 实例

参考答案


原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。


构造函数: 可以通过new来 新建一个对象的函数。


实例: 通过构造函数和new创建出来的对象,便是实例。实例通过__proto__指向原型,通过constructor指向构造函数。


这里来举个栗子,以Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。


// 实例

const instance = new Object()


则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:


// 原型

const prototype = Object.prototype


这里我们可以来看出三者的关系:


实例.__proto__ === 原型


原型.constructor === 构造函数


构造函数.prototype === 原型


// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线

// 例如:

// const o = new Object()

// o.constructor === Object --> true

// o.__proto__ = null;

// o.constructor === Object --> false

实例.constructor === 构造函数


8、简述一下JS继承,并举例

参考答案


在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。


最优化: 圣杯模式


var inherit = (function(c,p){

var F = function(){};

return function(c,p){

F.prototype = p.prototype;

c.prototype = new F();

c.uber = p.prototype;

c.prototype.constructor = c;

}

})();

使用 ES6 的语法糖 class / extends


9、函数柯里化

参考答案


在函数式编程中,函数是一等公民。那么函数柯里化是怎样的呢?


函数柯里化指的是将能够接收多个参数的函数转化为接收单一参数的函数,并且返回接收余下参数且返回结果的新函数的技术。


函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。


在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。


const add = function add(x) {

return function (y) {

return x + y

}

}


const add1 = add(1)


add1(2) === 3

add1(20) === 21


10、说说bind、call、apply 区别?

参考答案


call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。


除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。


let a = {

value: 1

}

function getValue(name, age) {

console.log(name)

console.log(age)

console.log(this.value)

}

getValue.call(a, 'yck', '24')

getValue.apply(a, ['yck', '24'])


bind和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind实现柯里化。


(下面是对这三个方法的扩展介绍)


如何实现一个 bind 函数


对于实现以下几个函数,可以从几个方面思考


不传入第一个参数,那么默认为 window


改变了 this 指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除?


Function.prototype.myBind = function (context) {

if (typeof this !== 'function') {

throw new TypeError('Error')

}

var _this = this

var args = [...arguments].slice(1)

// 返回一个函数

return function F() {

// 因为返回了一个函数,我们可以 new F(),所以需要判断

if (this instanceof F) {

return new _this(...args, ...arguments)

}

return _this.apply(context, args.concat(...arguments))

}

}


如何实现一个call函数


Function.prototype.myCall = function (context) {

var context = context || window

// 给 context 添加一个属性

// getValue.call(a, 'yck', '24') => a.fn = getValue

context.fn = this

// 将 context 后面的参数取出来

var args = [...arguments].slice(1)

// getValue.call(a, 'yck', '24') => a.fn('yck', '24')

var result = context.fn(...args)

// 删除 fn

delete context.fn

return result

}


如何实现一个apply函数


Function.prototype.myApply = function (context) {

var context = context || window

context.fn = this


var result

// 需要判断是否存储第二个参数

// 如果存在,就将第二个参数展开

if (arguments[1]) {

result = context.fn(...arguments[1])

} else {

result = context.fn()

}


delete context.fn

return result

}


11、箭头函数的特点

参考答案

function a() {

return () => {

return () => {

console.log(this)

}

}

}

console.log(a()()())


箭头函数其实是没有 this的,这个函数中的 this只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a符合前面代码中的第一个情况,所以 this是 window。并且 this一旦绑定了上下文,就不会被任何代码改变。


程序阅读题

1、下面程序输出的结果是什么?

function sayHi() {

console.log(name);

console.log(age);

var name = "Lydia";

let age = 21;

}


sayHi();


A: Lydia 和 undefined


B: Lydia 和 ReferenceError


C: ReferenceError 和 21


D: undefined 和 ReferenceError


参考答案


在函数中,我们首先使用var关键字声明了name变量。这意味着变量在创建阶段会被提升(JavaScript会在创建变量创建阶段为其分配内存空间),默认值为undefined,直到我们实际执行到使用该变量的行。我们还没有为name变量赋值,所以它仍然保持undefined的值。


使用let关键字(和const)声明的变量也会存在变量提升,但与var不同,初始化没有被提升。在我们声明(初始化)它们之前,它们是不可访问的。这被称为“暂时死区”。当我们在声明变量之前尝试访问变量时,JavaScript会抛出一个ReferenceError。


关于let的是否存在变量提升,我们何以用下面的例子来验证:


let name = 'ConardLi'

{

console.log(name) // Uncaught ReferenceError: name is not defined

let name = 'code秘密花园'

}


let变量如果不存在变量提升,console.log(name)就会输出ConardLi,结果却抛出了ReferenceError,那么这很好的说明了,let也存在变量提升,但是它存在一个“暂时死区”,在变量未初始化或赋值前不允许访问。


变量的赋值可以分为三个阶段:


创建变量,在内存中开辟空间


初始化变量,将变量初始化为undefined


真正赋值


关于let、var和function:


let的「创建」过程被提升了,但是初始化没有提升。


var的「创建」和「初始化」都被提升了。


function的「创建」「初始化」和「赋值」都被提升了。


2、下面代码输出什么

var a = 10;

(function () {

console.log(a)

a = 5

console.log(window.a)

var a = 20;

console.log(a)

})()


依次输出:undefined -> 10 -> 20


在立即执行函数中,var a = 20; 语句定义了一个局部变量 a,由于js的变量声明提升机制,局部变量a的声明会被提升至立即执行函数的函数体最上方,且由于这样的提升并不包括赋值,因此第一条打印语句会打印undefined,最后一条语句会打印20。


由于变量声明提升,a = 5; 这条语句执行时,局部的变量a已经声明,因此它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10,


3、下面的输出结果是什么?

class Chameleon {

static colorChange(newColor) {

this.newColor = newColor;

}


constructor({ newColor = "green" } = {}) {

this.newColor = newColor;

}

}


const freddie = new Chameleon({ newColor: "purple" });

freddie.colorChange("orange");


A: orange


B: purple


C: green


D: TypeError


答案: D


colorChange方法是静态的。静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。由于freddie是一个子级对象,函数不会传递,所以在freddie实例上不存在freddie方法:抛出TypeError。


4、下面代码中什么时候会输出1?

var a = ?;

if(a == 1 && a == 2 && a == 3){

conso.log(1);

}

参考答案


因为==会进行隐式类型转换 所以我们重写toString方法就可以了


var a = {

i: 1,

toString() {

return a.i++;

}

}


if( a == 1 && a == 2 && a == 3 ) {

console.log(1);

}


5、下面的输出结果是什么?

var obj = {

'2': 3,

'3': 4,

'length': 2,

'splice': Array.prototype.splice,

'push': Array.prototype.push

}

obj.push(1)

obj.push(2)

console.log(obj)

参考答案


1.使用第一次push,obj对象的push方法设置 obj[2]=1;obj.length+=12.使用第二次push,obj对象的push方法设置 obj[3]=2;obj.length+=13.使用console.log输出的时候,因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印 4.打印时因为数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined



6、下面代码输出的结果是什么?

var a = {n: 1};

var b = a;

a.x = a = {n: 2};


console.log(a.x)

console.log(b.x)

参考答案


undefined {n:2}


首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。


7、下面代码的输出是什么?

function checkAge(data) {

if (data === { age: 18 }) {

console.log("You are an adult!");

} else if (data == { age: 18 }) {

console.log("You are still an adult.");

} else {

console.log(`Hmm.. You don't have an age I guess`);

}

}


checkAge({ age: 18 });

参考答案


Hmm.. You don't have an age I guess


在比较相等性,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。JavaScript检查对象是否具有对内存中相同位置的引用。


我们作为参数传递的对象和我们用于检查相等性的对象在内存中位于不同位置,所以它们的引用是不同的。


这就是为什么{ age: 18 } === { age: 18 }和 { age: 18 } == { age: 18 }返回 false的原因。


8、下面代码的输出是什么?

const obj = { 1: "a", 2: "b", 3: "c" };

const set = new Set([1, 2, 3, 4, 5]);


obj.hasOwnProperty("1");

obj.hasOwnProperty(1);

set.has("1");

set.has(1);

参考答案


true` `true` `false` `true

所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。这就是为什么obj.hasOwnProperty('1')也返回true。


上面的说法不适用于Set。在我们的Set中没有“1”:set.has('1')返回false。它有数字类型1,set.has(1)返回true。


9、下面代码的输出是什么?

// example 1

var a={}, b='123', c=123;

a[b]='b';

a[c]='c';

console.log(a[b]);


---------------------

// example 2

var a={}, b=Symbol('123'), c=Symbol('123');

a[b]='b';

a[c]='c';

console.log(a[b]);


---------------------

// example 3

var a={}, b={key:'123'}, c={key:'456'};

a[b]='b';

a[c]='c';

console.log(a[b]);

参考答案


这题考察的是对象的键名的转换。


对象的键名只能是字符串和 Symbol 类型。


其他类型的键名会被转换成字符串类型。


对象转字符串默认会调用 toString 方法。


// example 1

var a={}, b='123', c=123;

a[b]='b';

// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。

a[c]='c';

// 输出 c

console.log(a[b]);



// example 2

var a={}, b=Symbol('123'), c=Symbol('123');

// b 是 Symbol 类型,不需要转换。

a[b]='b';

// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。

a[c]='c';

// 输出 b

console.log(a[b]);



// example 3

var a={}, b={key:'123'}, c={key:'456'};

// b 不是字符串也不是 Symbol 类型,需要转换成字符串。

// 对象类型会调用 toString 方法转换成字符串 [object Object]。

a[b]='b';

// c 不是字符串也不是 Symbol 类型,需要转换成字符串。

// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。

a[c]='c';

// 输出 c

console.log(a[b]);


10、下面代码的输出是什么?

(() => {

let x, y;

try {

throw new Error();

} catch (x) {

(x = 1), (y = 2);

console.log(x);

}

console.log(x);

console.log(y);

})();

参考答案


1` `undefined` `2

catch块接收参数x。当我们传递参数时,这与变量的x不同。这个变量x是属于catch作用域的。


之后,我们将这个块级作用域的变量设置为1,并设置变量y的值。现在,我们打印块级作用域的变量x,它等于1。


在catch块之外,x仍然是undefined,而y是2。当我们想在catch块之外的console.log(x)时,它返回undefined,而y返回2。


11、下面代码的输出结果是什么?

function Foo() {

Foo.a = function() {

console.log(1)

}

this.a = function() {

console.log(2)

}

}

Foo.prototype.a = function() {

console.log(3)

}

Foo.a = function() {

console.log(4)

}

Foo.a();

let obj = new Foo();

obj.a();

Foo.a();

参考答案


输出顺序是 4 2 1


function Foo() {

Foo.a = function() {

console.log(1)

}

this.a = function() {

console.log(2)

}

}

// 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行


Foo.prototype.a = function() {

console.log(3)

}

// 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3


Foo.a = function() {

console.log(4)

}

// 现在在 Foo 上挂载了直接方法 a ,输出值为 4


Foo.a();

// 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以

// # 输出 4


let obj = new Foo();

/* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:

1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。

2. 在新对象上挂载直接方法 a ,输出值为 2。

*/


obj.a();

// 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,

// # 输出 2


Foo.a();

// 构建方法里已经替换了全局 Foo 上的 a 方法,所以

// # 输出 1


最后


如果觉得这篇文章还不错,三连支持一下吧~

文章转载:乐字节

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言