js面试题(一)
一,DOM事件
1.dom事件的级别 DOM0 element.onclilck = function(){} DOM2 element.addEventListener('click',function(){}) DOM2 element.addEventListener('keyup',function(){}),增加了键盘和鼠标事件 2.dom事件模型, 冒泡和捕获 3.dom事件流 首先捕获阶段从祖先元素到达目标阶段 然后冒泡阶段,从目标阶段到祖先元素的一个过程 4.dom事件捕获的具体流程,
document 》html 》 body 》 (div)
5.事件委派 指将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素 从而通过祖先元素的响应函数来处理事件。
事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能 场景;商品分类移动移除事件
6.event对象的常见应用 event.preventDefault(),阻止默认行为,a标签,form表单
event.stopPropagation(),阻止冒泡,场景,新闻中的的个人中心图标点击事件,不用跳转到新闻详情,跳转到个人中心页
event.target 和 event.currentTarget
event.target 是引发事件的 目标元素(原始点击位置),它在冒泡过程中不会发生变化。
event.currentTarget 是当前正在执行的监听函数所在的那个节点,在冒泡过程中值会发发生变化,
event.currentTarget === this(this 指向触发事件的内部那个绑定监听函数的元素节点对象)。
举例
<ul id="ul"> <li class="item1">li item1</li> <li class="item2">li item2</li> <li class="item3">li item3</li> </ul> <script> var ul = document.getElementById("ul") ul.addEventListener('click', function (event) { console.log(this); console.log(event.target); // 点击哪个li就获取那个对应的li对象 console.log(event.currentTarget); // 每次都是最外层的ul对象,完全等于this console.log("------------------"); }, false); </script>
7.dom自定义事件
//创建事件, Event是无法传递参数的
var event = new Event('build');
//创建事件, CustomEvent是可以传递参数的
var event = new CustomEvent('build', { detail: elem.dataset.time });
// 监听事件Listen for the event.
elem.addEventListener('build', function (e) { //... }, false);
// 分发/触发事件Dispatch the event.
elem.dispatchEvent(event);
举例
// 事件对象eve,事件名称test var eve = new Event('test'); ev.addEventListener('test', function () { console.log('test dispatch'); }); setTimeout(function () { // 触发事件对象 ev.dispatchEvent(eve); }, 1000);
二,原型链
创建对象有几种方法
// 第一种方式:字面量 var o1 = {name: 'o1'}; var o2 = new Object({name: 'o2'}); // 第二种方式:构造函数 function M(name) { this.name = name; }; var o3 = new M('o3'); // 第三种方式:Object.create var p = {name: 'p'}; var o4 = Object.create(p);
原型链详情;https://www.cnblogs.com/fsg6/p/12773775.html
三,面向对象
类的申明
/** * 类的声明 */ var Animal = function () { this.name = 'Animal'; }; /** * es6中class的声明 */ class Animal2 { constructor () { this.name = 'Animal2'; } }
类生成实例
/** * 实例化 */ console.log(new Animal(), new Animal2());
如何实现继承,继承的几种方法,详解;https://www.cnblogs.com/fsg6/p/12792788.html
4.JS有几种数据类型,其中基本数据类型有哪些!
七种数据类型
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ECMAScript 6 新定义)
- Object
(ES6之前)其中5种为基本类型:string
,number
,boolean
,null
,undefined
,
ES6出来的Symbol
也是原始数据类型 ,表示独一无二的值
Object
为引用类型(范围挺大),也包括数组、函数,
5.null
和undefined
的差异
相同点:
- 在
if
判断语句中,值都默认为false
- 大体上两者都是代表无,具体看差异
差异:
null
转为数字类型值为0,而undefined
转为数字类型为NaN(Not a Number)
undefined
是代表调用一个值而该值却没有赋值,这时候默认则为undefined,函数没有返回值时,默认返回undefined。
null
是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)- 设置为
null
的变量或者对象会被内存收集器回收
6.JS 的DOM 操作(Node节点获取及增删查改);
- 获取(太多了,有
document.getElementById/ClassName/Name/TagName 等,或者 querySelector
)
// example // get Node var element = document.querySelector('#test'); // 追加 element.appendChild(Node); // 删除 element.removeChild(Node); // 查找 element.nextSibling // 获取元素之后的兄弟节点 , 会拿到注释文本,空白符这些 element.nextElementSibling // 等同, 获取标签(不会拿到注释文本这些) element.previousSibling // 和上面同理,往前找兄弟节点 element.previousElementSibling // 改动,比如 属性这些 element.setAttribute(name, value); // 增加属性 element.removeAttribute(attrName); //删除属性
7.Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?
hasOwnProperty
,这个更多的是用来区分自身属性和原型链上的属性。Object的hasOwnProperty()
方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
function foo() { this.name = 'foo' this.sayHi = function () { console.log('Say Hi') } } foo.prototype.sayGoodBy = function () { console.log('Say Good By') } let myPro = new foo() console.log(myPro.name) // foo console.log(myPro.hasOwnProperty('name')) // true console.log(myPro.hasOwnProperty('toString')) // false,不会去查找原型中的属性 console.log(myPro.hasOwnProperty('hasOwnProperty')) // fasle console.log(myPro.hasOwnProperty('sayHi')) // true console.log(myPro.hasOwnProperty('sayGoodBy')) // false console.log('sayGoodBy' in myPro) // true
8.谈谈你对ajax 的理解,以及用原生 JS 实现有哪些要点需要注意;
ajax
全称是异步 javascript 和 XML
,用来和服务端进行数据交互的,让无刷新替换页面数据成了可能;
至于有哪些要要点,来一个简短的ajax
请求
var xhr = new XMLHttpRequest(); // 声明一个请求对象 xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ // readyState 4 代表已向服务器发送请求 if(xhr.status === OK){ // // status 200 代表服务器返回成功 console.log(xhr.responseText); // 这是返回的文本 } else{ console.log("Error: "+ xhr.status); // 连接失败的时候抛出错误 } } } //发送http请求 xhr.open('GET', 'xxxx'); // 如何设置请求头? xhr.setRequestHeader(header, value); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(null); // get方法 send null(亦或者不传,则直接是传递 header) ,post 的 send 则是传递值
9.this对象的理解,详情;https://www.cnblogs.com/dongcanliang/p/7054176.html
js的this指向是不确定的,也就是说是可以动态改变的。call/apply 就是用于改变this指向的函数,谁调用指向谁
(1)、处于全局作用域下的this: this;/*window*/ var a = {name: this}/*window*/ var b = [this];/*window*/ 在全局作用域下,this默认指向window对象。 (2)、处在函数中的this,又分为以下几种情况: a、一般定义的函数,然后一般的执行: var a = function(){ console.log(this); } a();/*window*/ this还是默认指向window。 b、一般定义,用new调用执行: var a = function(){ console.log(this); } new a();/*新建的空对象*/ 这时候让this指向新建的空对象,我们才可以给空对象初始化自有变量 c、作为对象属性的函数,调用时: var a = { f:function(){ console.log(this) } } a.f();/*a对象*/ 这时候this指向调用f函数的a对象。 (3)、通过call()和apply()来改变this的默认引用: var b = {id: 'b'}; var a = { f:function(){ console.log(this) } } a.f.call(b);/*b*/ 所有函数对象都有的call方法和apply方法,它们的用法大体相似,f.call(b);的意思 是,执行f函数,并将f函数执行期活动对象里的this指向b对象,这样标示符解析时,this就会是b对象了。
不过调用函数是要传参的。所以,f.call(b, x, y); f.apply(b, [x, y]);好吧,以上就是用call方法执行f函数,与用apply方法执行f函数时传参方式,它们之间的差异,大家一目了然:
apply通过数组的方式传递参数,call通过一个个的形参传递参数
function add(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
bind方法
bind方法在ES5引入, 在Function的原型链上, Function.prototype.bind
。通过bind方法绑定后, 函数将被永远绑定在其第一个参数对象上, 而无论其在什么情况下被调用。
function f() { return this; } var g = f.bind({ a: "azerty" }); console.log(g()); // { a: "azerty" }
(4)setTimeout & setInterval
对于延时函数内部的回调函数的this指向全局对象window(当然我们可以通过bind方法改变其内部函数的this指向)
//默认情况下代码 function Person() { this.age = 0; setTimeout(function() { console.log(this); }, 3000); } var p = new Person();//3秒后返回 window 对象 ============================================== //通过bind绑定 function Person() { this.age = 0; setTimeout((function() { console.log(this); }).bind(this), 3000); } var p = new Person();//3秒后返回构造函数新生成的对象 Person{...}
箭头函数中的 this
由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值
function Person() { this.age = 0; setTimeout(() => { // 回调里面的 `this` 变量就指向了期望的那个对象了 console.log(this);//实例对象p this.age++; }, 3000); } var p = new Person(); //实例对象p
var p = () => { return this; }; p()//window
以上箭头函数都在方法内部,回去外层的函数找this值
箭头函数作为对象属性调用时,可以看到,作为方法调用的箭头函数this指向全局window对象,而普通函数则指向调用它的对象
var obj = { i: 10, b: () => console.log(this.i, this), c: function() { console.log( this.i, this) } } obj.b(); // undefined window{...} obj.c(); // 10 Object {...}
b、dom模型中触发事件的回调方法执行中活动对象里的this指向该dom对象。
10.JS 的作用域是什么?有什么特别之处么?
JS的作用域主要是说变量的作用范围。作用域是函数定义好就存在的,而作用域链是函数调用的时候才有的;
JS的作用域从大类分的话是两种:全局作用域和局部作用域
。内部会有变量声明提升
作用域链描述的是程序查找变量的过程,首先在自己的作用域当中去查找,如果查找不到,去到上级作用域去查找,查找就用,查不到继续往上查找,直到找到真正的全局
1.js作用域(全局变量,局部变量)内部可以访问外部,但外部的不能访问内部的
var a=10; function aaa(){ alert(a); }; aaa(); //a 为外部变量即全局变量,所以可以直接访问到 结果为10
function aaa(){ var a=10; }; aaa(); alert(a); //a 为函数aaa()内部变量量即局部变量,所以无法访问到
var a=10; function aaa(){ alert(a); }; function bbb(){ var a=20; aaa(); } bbb(); //结果为10,
//函数定义时候作用域就定死了,函数的作用域和调用没关系
2.变量的查找是就近原则去寻找,定义的var变量;第二点,变量的声明被提前到作用域顶部,赋值保留在原地
function aaa(){ alert(a); var a=20; } aaa(); //结果为:undefined /**************/ var a=10; function aaa(){ alert(a); var a=20; } aaa(); //结果为:undefined 可以解析为是: var a=10; function aaa(){ var a; //声明提前了 alert(a); a=20; //赋值扔留着原地 } aaa();
3.如果函数内部没有定义var这个变量,找形参,形参如果有,当做局部变量处理
var a=10; function aaa(a){ alert(a); var a=20; } aaa(a); //结果为:10
11.描述下cookie
,sessionStorage
,localStorage
的差异..
共同点:都是保存在浏览器端、且同源的
1.cookie大小4KB 左右,跟随请求(请求头),会占用带宽资源,但是若是用来判断用户是否在线这些挺方便,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
2.数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
3.作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
12.javascript
的原型链你怎么理解?以及继承怎么实现(手写), 详解;https://www.cnblogs.com/fsg6/p/12773775.html
原型链算是 JS 内一种独有的机制,
所有对象都有一个内置[[proto]]
指向创建它的原型对象(prototype
)
原型链的基本用来实现继承用的
13.ES6+你熟悉么,用过哪些特性?
- 箭头函数
- 类及引入导出和继承(
class
/import
/export
/extends
) - 字符串模板
- Promise
let
,const
async
/await
- 默认参数/参数或变量解构装饰器
Array.inclueds
/String.padStart|String.padEnd
/Object.assign
14. var,let 和 const 有啥差异?
let
会产生块级作用域,不会造成变量提升,无法重新声明(但可以重新赋值);const
- 是只读的,若是基本数据类型,具有不变性(无法重新赋值改动)
- 引用对象可以调整内部值(可能设计的时候没有考虑周全!)
1.let
命令,用来声明变量。只在块级代码块有效。var是在全局变量
if (true) { let b= 2;
var a=1 } console.log(b);//报错
console.log(a) //1
2.不存在变量提升,var
命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined
// var 的情况 console.log(a); // 输出undefined var a = 2; // let 的情况 console.log(b); // 报错ReferenceError let b = 2
3.let
不允许在相同作用域内,重复声明同一个变量,但是可以更改值,var命令可以重复申明变量
let a =1 let a=2 //报错
a=2//正常
var b=1 var b =2 //正常
const命令
1. const
声明一个只读的常量。一旦声明,常量的值就不能改变。如果是引用对象,可修改内部的属性
const a ;//报错,一旦声明变量,应该立即赋值!! const b = 2; b = 3//报错,因为定义常量之后不能成重新赋值!! const names = []; names = [1,2,3] //出错,因为变量names指向的地址不能发生改变,应始终指向[]所在的地址!!![1,2,3]与[]不是同一个地址 //不会报错,因为names指向的地址不变,改变的只是内部数据 const names = []; names[0] = 1 names[1] = 2 names[2] = 3
2.const
命令声明的常量也是不提升
3.const
的作用域与let
命令相同:只在声明所在的块级作用域内有效
15.async
和await
的用途
await有两个作用,一是作为求值关键字,二是用同步方式展示异步代码;如果方法中使用了await,那么在方法前面必须加上async
1.await可以阻塞当前线程,将异步操作变成同步,发送ajax请求,必须数据请求到了,执行下步操作
2.释放异常(抛出异常)
await可以进行求值操作,当await后面跟的是Promise时,如果Promise中的操作是resolve(成功),那么await获取的就是操作成功时的返回值;如果Promise中的操作是reject(失败),await获取的就是操作失败时的返回值,并且如果在await上加了try catch,是可以捕捉到异常的
async function fun1() { try { // 必须加await,不然会报错:UnhandledPromiseRejectionWarning await fun2(); } catch (error) { console.log("error") } } function fun2() { return new Promise((resolve, reject) => { reject("error"); }) } fun1();
3.async的作用是将方法的返回值封装成Promise
async function t() { return "hello"; } console.log(t()); // 打印出 Promise{"hello"}
16.谈谈你对 Promise 的理解? 和 ajax 有关系么?,promise题目;https://juejin.cn/post/6844903509934997511
Promise
和ajax
没有半毛钱直接关系.promise
只是为了解决"回调地狱"而诞生的;
1、Promise 是一个构造函数,我们可以通过该构造函数来生成Promise的实例。Promise 构造函数是同步执行的,promise.then
中的函数是异步执行的。
const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4)
//打印顺序,1 2 4 3
2、Promise 状态有三:pending(等待)、resolved(成功)、rejected(失败)。状态改变只能是 pending->resolved 或者 pending->rejected,状态一旦改变则不能再变
3、Promise 是对回调函数的一种封装,解决对调地狱的问题,我们可以通过Promise将自己的程序以同步的方式表达出来,从而可以解决代码臃肿及可读性差的问题。
4、axios采用Promise对象,发送ajax请求,获取数据,利用async和awiat方式类同步获取数据
5、Promise虽然解决了我们项目开发中的很多问题,但我们也不能无脑的滥用。比如Promise.all,如果参数中promise有一个失败(rejected),则此实例回调必然失败(reject),就不会再执行then方法的回调了。在实际中可能只是一个不关键的数据加载失败,往往会导致其他所有的数据不会显示,使得项目的容错性大大降低。所以我个人在开发过程中只会在必须依赖这几个步骤全部加载成功后才能继续向下执行的场景中采用它,比如图片的预加载功能。
基础点
- 成功调用
resolve
,失败调用reject
.then
获取结果,.catch
捕获异常。捕获异常还可通过.then
的第二个参数.finally
无论成功失败都一定会调用- 多个并发的请求,用
Promise.all()
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
- 只有