JS前端面试题(三)

文章目录

61.new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?

new:

  1. 创建一个新对象。
  2. 这个新对象会被执行[[原型]]连接。
  3. 将构造函数的作用域赋值给新对象,即this指向这个新对象.
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function new(func) {
    
    
    lat target = {
    
    };
    target.__proto__ = func.prototype;
    let res = func.call(target);
    if (typeof(res) == "object" || typeof(res) == "function") {
    
    
        return res;
    }
    return target;
}

字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;

new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

通过对象字面量定义对象时,不会调用Object构造函数。

62.prototype 和 proto 区别是什么?

prototype是构造函数的属性。

__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性。

实例的__proto__与其构造函数的prototype指向的是同一个对象。

function Student(name) {
    
    
    this.name = name;
}
Student.prototype.setAge = function(){
    
    
    this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true

63.使用ES5实现一个继承?

组合继承(最常用的继承方式)

function SuperType() {
    
    
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
    
    
    console.log(this.name);
}

function SubType(name, age) {
    
    
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function() {
    
    
    console.log(this.age);
}

64.取数组的最大值(ES5、ES6)

// ES5 的写法
Math.max.apply(null, [14, 3, 77, 30]);

// ES6 的写法
Math.max(...[14, 3, 77, 30]);

// reduce
[14,3,77,30].reduce((accumulator, currentValue)=>{
    
    
    return accumulator = accumulator > currentValue ? accumulator : currentValue
});

65.ES6新的特性有哪些?

  1. 新增了块级作用域(let,const)
  2. 提供了定义类的语法糖(class)
  3. 新增了一种基本数据类型(Symbol)
  4. 新增了变量的解构赋值
  5. 函数参数允许设置默认值,引入了rest参数,新增了箭头函数
  6. 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法
  7. 对象和数组新增了扩展运算符
  8. ES6 新增了模块化(import/export)
  9. ES6 新增了 Set 和 Map 数据结构
  10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
  11. ES6 新增了生成器(Generator)和遍历器(Iterator)

66.promise 有几种状态, Promise 有什么优缺点 ?

promise有三种状态: fulfilled, rejected, pending.

Promise 的优点:

  1. 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  2. 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

Promise 的缺点:

  1. 无法取消 Promise
  2. 当处于pending状态时,无法得知目前进展到哪一个阶段

67.Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ?

Promise的构造函数是同步执行的。then 是异步执行的。

68.Promise和setTimeout的区别 ?

Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise.then总是先于 setTimeout 执行。

69.如何实现 Promise.all ?

要实现 Promise.all,首先我们需要知道 Promise.all 的功能:

  1. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
  2. 如果传入的参数不包含任何 promise,则返回一个异步完成.

promises 中所有的promise都“完成”时或参数中不包含 promise 时回调完成。

  1. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
  2. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all = function (promises) {
    
    
    return new Promise((resolve, reject) => {
    
    
        let index = 0;
        let result = [];
        if (promises.length === 0) {
    
    
            resolve(result);
        } else {
    
    
            setTimeout(() => {
    
    
                function processValue(i, data) {
    
    
                    result[i] = data;
                    if (++index === promises.length) {
    
    
                        resolve(result);
                    }
                }
                for (let i = 0; i < promises.length; i++) {
    
    
                    //promises[i] 可能是普通值
                    Promise.resolve(promises[i]).then((data) => {
    
    
                        processValue(i, data);
                    }, (err) => {
    
    
                        reject(err);
                        return;
                    });
                }
            })
        }
    });
}

70.如何实现 Promise.finally ?

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

Promise.prototype.finally = function (callback) {
    
    
    return this.then((value) => {
    
    
        return Promise.resolve(callback()).then(() => {
    
    
            return value;
        });
    }, (err) => {
    
    
        return Promise.resolve(callback()).then(() => {
    
    
            throw err;
        });
    });
}

71.如何判断img加载完成

一、load事件


<!DOCTYPE HTML>

<html> 

<head> 

    <meta charset="utf-8">

    <title>img - load event</title>

</head> 

<body>

    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">

    <p id="p1">loading...</p>

    <script type="text/javascript">

        img1.onload = function() {
    
    

            p1.innerHTML = 'loaded'

        }

    </script>

</body>

</html> 

测试,所有浏览器都显示出了“loaded”,说明所有浏览器都支持img的load事件。

二、readystatechange事件

<!DOCTYPE HTML>

<html> 

<head> 

    <meta charset="utf-8">

    <title>img - readystatechange event</title>

</head> 

<body>

    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">

    <p id="p1">loading...</p>

    <script type="text/javascript">

        img1.onreadystatechange = function() {
    
    

            if(img1.readyState=="complete"||img1.readyState=="loaded"){
    
     

                p1.innerHTML = 'readystatechange:loaded'

            }

        }

    </script>

</body>

</html> 

readyState为complete和loaded则表明图片已经加载完毕。测试IE6-IE10支持该事件,其它浏览器不支持。

三、img的complete属性

<!DOCTYPE HTML>

<html> 

<head> 

    <meta charset="utf-8">

    <title>img - complete attribute</title>

</head> 

<body>

    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">

    <p id="p1">loading...</p>

    <script type="text/javascript">

        function imgLoad(img, callback) {
    
    

            var timer = setInterval(function() {
    
    

                if (img.complete) {
    
    

                    callback(img)

                    clearInterval(timer)

                }

            }, 50)

        }

        imgLoad(img1, function() {
    
    

            p1.innerHTML('加载完毕')

        })

    </script>

</body>

</html> 

轮询不断监测img的complete属性,如果为true则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。

72.如何阻止冒泡?

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。

//阻止冒泡行为 
function stopBubble(e) {
    
     
//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //因此它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //否则,我们需要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}

73.如何阻止默认事件?

w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

//阻止浏览器的默认行为 
function stopDefault( e ) {
    
     
    //阻止默认浏览器动作(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函数器默认动作的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}

74.ajax请求时,如何解释json数据

  • 使用eval parse,但是鉴于安全性考虑 使用parse更靠谱;

75.json和jsonp的区别?

json返回的是一串json格式数据;而jsonp返回的是脚本代码(包含一个函数调用)

jsonp的全名叫做json with padding,就是把json对象用符合js语法的形式包裹起来以使其他的网站可以请求到,也就是将json封装成js文件传过去。

76.如何用原生js给一个按钮绑定两个onclick事件?

Var  btn=document.getElementById(‘btn’);
//事件监听 绑定多个事件
var btn4 = document.getElementById("btn4");
btn4.addEventListener("click",hello1);
btn4.addEventListener("click",hello2);
function hello1(){
    
    
 alert("hello 1");
}
function hello2(){
    
    
 alert("hello 2");
}

77.拖拽会用到哪些事件

· dragstart:拖拽开始时在被拖拽元素上触发此事件,监听器需要设置拖拽所需数据,从操作系统拖拽文件到浏览器时不触发此事件.

· dragenter:拖拽鼠标进入元素时在该元素上触发,用于给拖放元素设置视觉反馈,如高亮

· dragover:拖拽时鼠标在目标元素上移动时触发.监听器通过阻止浏览器默认行为设置元素为可拖放元素.

· dragleave:拖拽时鼠标移出目标元素时在目标元素上触发.此时监听器可以取消掉前面设置的视觉效果.

· drag:拖拽期间在被拖拽元素上连续触发

· drop:鼠标在拖放目标上释放时,在拖放目标上触发.此时监听器需要收集数据并且执行所需操作.如果是从操作系统拖放文件到浏览器,需要取消浏览器默认行为.

· dragend:鼠标在拖放目标上释放时,在拖拽元素上触发.将元素从浏览器拖放到操作系统时不会触发此事件.

78.document.write和innerHTML的区别

document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。

innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。

innerHTML将内容写入某个DOM节点,不会导致页面全部重绘

innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。

79.jQuery的事件委托方法bind 、live、delegate、on之间有什么区别?

(1)、bind 【jQuery 1.3之前】

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

语法:bind(type,[data],function(eventObject));

特点:

(1)、适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。

(2)、当页面加载完的时候,你才可以进行bind(),所以可能产生效率问题。

实例如下:$( “#members li a” ).bind( “click”, function( e ) {} );

(2)、live 【jQuery 1.3之后】

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

语法:live(type, [data], fn);

特点:

(1)、live方法并没有将监听器绑定到自己(this)身上,而是绑定到了this.context上了。

(2)、live正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了document,新添加的元素不必再绑定一次监听器。

(3)、使用live()方法但却只能放在直接选择的元素后面,不能在层级比较深,连缀的DOM遍历方法后面使用,即 ( “ u l ” " ) . l i v e . . . 可 以 , 但 (“ul”").live...可以,但 (ul").live...(“body”).find(“ul”).live…不行;

实例如下:$( document ).on( “click”, “#members li a”, function( e ) {} );

(3)、delegate 【jQuery 1.4.2中引入】

定义和用法:将监听事件绑定在就近的父级元素上

语法:delegate(selector,type,[data],fn)

特点:

(1)、选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。

(2)、更精确的小范围使用事件代理,性能优于.live()。可以用在动态添加的元素上。

实例如下:

$("#info_table").delegate(“td”,“click”,function(){/显示更多信息/});

$(“table”).find("#info").delegate(“td”,“click”,function(){/显示更多信息/});

(4)、on 【1.7版本整合了之前的三种方式的新事件绑定机制】

定义和用法:将监听事件绑定到指定元素上。

语法:on(type,[selector],[data],fn)

实例如下:$("#info_table").on(“click”,“td”,function(){/显示更多信息/});参数的位置写法与delegate不一样。

说明:on方法是当前JQuery推荐使用的事件绑定方法,附加只运行一次就删除函数的方法是one()。

总结 :.bind(), .live(), .delegate(),.on()分别对应的相反事件为:.unbind(),.die(), .undelegate(),.off()

80.浏览器是如何渲染页面的?

渲染的流程如下:

1.解析HTML文件,创建DOM树。

自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。

2.解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;

3.将CSS与DOM合并,构建渲染树(Render Tree)

4.布局和绘制,重绘(repaint)和重排(reflow)

81.$(document).ready()方法和window.onload有什么区别?

(1)、window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。

(2)、$(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。

82. jquery中 . g e t ( ) 提 交 和 .get()提交和 .get().post()提交有区别吗?

相同点:都是异步请求的方式来获取服务端的数据;

异同点:

1、请求方式不同: . g e t ( ) 方 法 使 用 G E T 方 法 来 进 行 异 步 请 求 的 。 .get() 方法使用GET方法来进行异步请求的。 .get()使GET.post() 方法使用POST方法来进行异步请求的。

2、参数传递方式不同:get请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给Web服务器的,这种传递是对用户不可见的。

3、数据传输大小不同:get方式传输的数据大小不能超过2KB 而POST要大的多

4、安全问题: GET 方式请求的数据会被浏览器缓存起来,因此有安全问题。

83.对前端路由的理解?前后端路由的区别?

前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。

http://10.0.0.1/
http://10.0.0.1/#/about
http://10.0.0.1/#/concat

服务端路由:每跳转到不同的URL,都是重新访问服务端,然后服务端返回页面,页面也可以是服务端获取数据,然后和模板组合,返回HTML,也可以是直接返回模板HTML,然后由前端JS再去请求数据,使用前端模板和数据进行组合,生成想要的HTML。

前端路由:每跳转到不同的URL都是使用前端的锚点路由,实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合,当然模板有可能是请求服务端返回的,这就是 SPA 单页程序。

在js可以通过window.location.hash读取到路径加以解析之后就可以响应不同路径的逻辑处理。

history 是 HTML5 才有的新 API,可以用来操作浏览器的 session history (会话历史)。基于 history 来实现的路由可以和最初的例子中提到的路径规则一样。

H5还新增了一个hashchange事件,也是很有用途的一个新事件:

当页面hash(#)变化时,即会触发hashchange。锚点Hash起到引导浏览器将这次记录推入历史记录栈顶的作用,window.location对象处理“#”的改变并不会重新加载页面,而是将之当成新页面,放入历史栈里。并且,当前进或者后退或者触发hashchange事件时,我们可以在对应的事件处理函数中注册ajax等操作!
但是hashchange这个事件不是每个浏览器都有,低级浏览器需要用轮询检测URL是否在变化,来检测锚点的变化。当锚点内容(location.hash)被操作时,如果锚点内容发生改变浏览器才会将其放入历史栈中,如果锚点内容没发生变化,历史栈并不会增加,并且也不会触发hashchange事件。

84.手写一个类的继承

继承的形式有很多中,js高程里面归纳了其中,我简单说一下前三种。
1.原型继承

function Parent(){
    
    
    this.name = "parent";
}
Parent.prototype.getName = function(){
    
    
    return this.name;
}

function Child(){
    
    
    this.name = "child";
}

//继承parent
Child.prototype = new Parent();

2.构造函数继承

function Animal(name){
    
    
    this.name = name;
    this.eat = function(){
    
    
        consoel.log(this.name + "吃饭");
    }
}
var cat = new Animal("maomi");
cat.name;
cat.eat();

85.XMLHttpRequest:XMLHttpRequest.readyState;状态码的意思

XmlHttpRequest(Ajax)状态码readyState
当一个XMLHttpRequest初次创建时,这个属性的值是从0开始,知道接收完整的HTTP响应,这个值增加到4。有五种状态:

状态0 (请求未初始化): (XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法;

状态1 (载入服务器连接已建立):已经调用open() 方法,但是send()方法未调用,尚未发送请求;

状态2 (载入完成,请求已接收): send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应;

状态3 (交互,请求处理中):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据;

状态4 (请求完成,且相应已就绪):已经接收到了全部数据,并且连接已经关闭。

86.请重写Array 原型的map方法

先看一下map的用法

//语法:array.map(function(currentValue,index,arr), thisValue)
//用法:
var arr = [1,2,3,4];
arr.map((item,index,arr) => {
    
    
    return item*10 //新数组为10,20,30,40
})
//map遍历数组,返回一个新数组,不改变原数组的值。

用js实现

Array.prototype.fakeMap = function(fn,context) {
    
    
	let arr = this;
	let temp = [];
	for(let i=0;i<arr.length;i++){
    
    
		let result = fn.call(context,arr[i],i,arr);
		temp.push(result);
	}
	return temp;
}

87.js编程题

1.请写出代码的运行结果?如何改进?

请写出代码运行结果?如何改进?

for(var i=0;i<5;i++){
    
    
		setTimeout(function(){
    
    
			console.log(i)
		},1000)
	}

//输出结果为(五个5)

原因是

settimeout是异步执行,1s后往任务队列里面添加一个任务,
只有主线上的全部执行完,才会执行任务队列里的任务,
当主线执行完成后,i是5,所以此时再去执行任务队列里的任务时,i全部是5了。

改进

for(let i=0;i<5;i++){
    
    
		setTimeout(function(){
    
    
			console.log(i)
		},1000)
	}

//输出 0,1,2,3,4
解析

for循环头部的let不仅将i绑定到for循环块中,
它也将其重新绑定到 **循环体的每一次迭代** 中,确保上一次迭代结束的值重新被赋值。

setTimeout里面的function()属于一个新的域,
通过 var 定义的变量是无法传入到这个函数执行域中的,
而通过使用 let 来声明块变量,这时候变量就能作用于这个块,
所以 function就能使用 i 这个变量了;

2.请写出打印结果,并解释为什么?

请写出代码运行结果,并解释为什么?

let length = 10;

function fn(){
    
    
	console.log(this.length);
}
var obj = {
    
    
	length:5,
	method:function(){
    
    
		return fn;
	}
}
obj.method()();	

//输出结果:0
解析:
“this永远指向调用他的对象”,在执行obj.method()方法时,如果函数内部有this,则this确实是指向obj,但是method()内部执行的是fn()函数,而fn()函数绑定的对象是window,即window.fn()
所以this指的事window
但是为什么没有显示10而是显示的0是因为
let声明变量会形成块级作用域,且不存在声明提升,而var存在声明提升。所以当使用let声明变量时,不存在声明提升,length属性实际上并没有添加到window对象中。

3.请写出打印结果,并解释为什么?

请写出代码运行结果,并解释为什么?

var name = "clobal";
	  var object = {
    
    
	   name:'local',
	   fn:function(){
    
    
	    return this.name;
	   },
	   getName:function(){
    
    
	    alert("1." + this.name);
	    return function(){
    
    
	     return this.name;
	    }
	   },
	   getFn:function(){
    
    
	    return this.fn;
	   }
	  };
	  var fun = object.getName();
	  alert("2."+fun());
	  alert("3."+fun.apply(object));
	  alert("4."+fun.call(object));
	  var fn = object.getFn();
	  alert("5."+fn())

输出结果

1.local 2.clocal 3.local 4.local 5.clocal

解析
this对象是在运行时基于函数的执行环境绑定的,
匿名函数的执行环境具有全局性,
因此匿名函数的this指向window

猜你喜欢

转载自blog.csdn.net/hrj970808/article/details/109645168