JS:js基础常见面试题

您所在的位置:网站首页 姥姥和姥爷是什么关系 JS:js基础常见面试题

JS:js基础常见面试题

2024-02-08 12:43| 来源: 网络整理| 查看: 265

一、类型

①类型:Number、String、Boolean、Null、undefined、object、symbol、bigInt

基本数据类型:Object。 String、Number、boolean、null、undefined 引用类型:object。里面包含的 function、Array

②typeof输出

类型结果Undefined"undefined"Null"object"Boolean"boolean"Number"number"BigInt"bigint"String"string"Symbol"symbol"function"function"{}、[]object

③如何判断数据类型

typeof、toString()、toLocaleString()、检测数组类型的方法(instanceof操作符、对象的constructor属性、Array.isArray( )检验值是否为数组)

④null 和 undefined 的区别

Null 只有一个值,是 null。不存在的对象。

Undefined 只有一个值,是undefined。没有初始化。undefined 是从 null 中派生出来的。

简单理解就是:undefined 是没有定义的,null 是定义了但是为空。

二、var与let、const的区别

①var声明的变量会挂载在window上,而let和const声明的变量不会:

var a = 100; console.log(a,window.a); // 100 100 let b = 10; console.log(b,window.b); // 10 undefined const c = 1; console.log(c,window.c); // 1 undefined

②var声明变量存在变量提升,let和const不存在变量提升

console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值 var a = 100; console.log(b); // 报错:b is not defined ===> 找不到b这个变量 let b = 10; console.log(c); // 报错:c is not defined ===> 找不到c这个变量 const c = 10;

③let和const声明形成块作用域

if(1){ var a = 100; let b = 10; const c = 1; } console.log(a); // 100 console.log(b) // 报错:b is not defined ===> 找不到b这个变量 console.log(c) // 报错:c is not defined ===> 找不到c这个变量

④同一作用域下let和const不能声明同名变量,而var可以

var a = 100; console.log(a); // 100 var a = 10; console.log(a); // 10 let a = 100; const a = 10; // 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。

⑤暂存死区

var a = 100; if(1){ a = 10; //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a, // 而这时,还未到声明时候,所以控制台Error:a is not defined let a = 1; }

⑥const

// 1、 一旦声明必须赋值,不能使用null占位。 // 2、 声明后不能再修改 // 3、 如果声明的是复合类型数据,可以修改其属性 const a = 100; const list = []; list[0] = 10; console.log(list);  // [10] const obj = {a:100}; obj.name = 'apple'; obj.a = 10000; console.log(obj);  // {a:10000,name:'apple'} 三、 for of , for in 和 forEach,map 的区别。 for...of循环:具有 iterator 接口,就可以用for...of循环遍历它的成员(属性值)。for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象、Generator 对象,以及字符串。 for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。可以中断循环。 for...in循环:遍历对象自身的和继承的可枚举的属性, 不能直接获取属性值。可以中断循环。 forEach: 只能遍历数组,不能中断,没有返回值(或认为返回值是undefined)。 map: 只能遍历数组,不能中断,返回值是修改后的数组。

PS: Object.keys():返回给定对象所有可枚举属性的字符串数组。

关于forEach是否会改变原数组的问题,有些小伙伴提出了异议,为此我写了代码测试了下(注意数组项是复杂数据类型的情况)。 除了forEach之外,map等API,也有同样的问题。

四、this的理解

1、this是Javascript语言的一个关键字,它代表函数运行时自动生成的一个内部对象,只能在函数内部使用。this指的是调用函数的那个对象。his的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

this的指向不是在编写时确定的,而是在执行时确定的 默认指向全局,浏览器的话就是指向window 如果函数被调用的位置存在上下文,那么函数被隐式绑定 new指向新对象 箭头函数的是没有属于自己的this的,它所谓的this是捕获其上下文的this,作为自己的this,所以箭头函数不会被new调用,所谓的this也不会被改变

①改变this指向:call、apply、bind或者在function外面 用var that=this;来改变指向

②区别和联系

call()和apply()的区别:

相同点:都是调用一个对象的一个方法,用另一个对象替换当前对象(功能相同)

不同点:

①参数书写方式不同

②call()的第一个参数是this要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为null、undefined的时候,默认指向window;

③apply():第一个参数是this要指向的对象,第二个参数是数组

call()和bind()的区别:

相同点:都是用来改变this的指向

不同点:call()改过this的指向后,会再执行函数,bind()改过this后,不执行函数,会返回一个绑定新this的函数

五、闭包

  一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

function init() { var name = "Mozilla"; // name 是一个被 init 创建的局部变量 function displayName() { // displayName() 是内部函数,一个闭包 alert(name); // 使用了父函数中声明的变量 } displayName(); } init(); 闭包就是能够读取其他函数内部变量的函数。 让这些变量的值始终保持在内存中。 会导致内存泄漏(在退出函数之前,将不使用的局部变量全部删除,IE) 六、箭头函数和普通函数

  箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return。

箭头函数

let fun = () => { console.log('lalalala'); }

普通函数

function fun() { console.log('lalla'); } 箭头函数是匿名函数,不能作为构造函数,不能使用new 箭头函数不绑定arguments,取而代之用rest参数...解决 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值 箭头函数通过 call()或apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。 箭头函数没有原型属性 箭头函数不能当做Generator函数,不能使用yield关键字 总结 箭头函数的 this 永远指向其上下文的this ,任何方法都改变不了其指向,如 call() , bind() , apply() 普通函数的this指向调用它的那个对象 七、call、apply有什么区别?call,aplly和bind的内部是如何实现的? 1. call/apply/bind方法的来源

call,apply,bind这三个方法都是继承自Function.prototype中的,属于实例方法。

console.log(Function.prototype.hasOwnProperty('call')) //true console.log(Function.prototype.hasOwnProperty('apply')) //true console.log(Function.prototype.hasOwnProperty('bind')) //true

  上面代码中,都返回了true,表明三种方法都是继承自Function.prototype的。当然,普通的对象,函数,数组都继承了Function.prototype对象中的三个方法,所以这三个方法都可以在对象,数组,函数中使用。

2. Function.prototype.call() fn.call(obj, arg1, arg2, ...),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。   函数实例的call方法,可以指定该函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。并且会立即执行该函数。 function keith(a, b) { console.log(a + b); } keith.call(null, 1, 2); //3

  call()方法可以传递两个参数。第一个参数是指定函数内部中this的指向(也就是函数执行时所在的作用域),第二个参数是函数调用时需要传递的参数。

  第一个参数是必须的,可以是null,undefined,this,但是不能为空。设置为null,undefined,this表明函数keith此时处于全局作用域。第二个参数中必须一个个添加。而在apply中必须以数组的形式添加。

  call方法的一个应用是调用对象的原生方法。也可以用于将类数组对象转换为数组。

3. Function.prototype.apply() fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。   apply方法的作用与call方法类似,也是改变this指向(函数执行时所在的作用域),然后在指定的作用域中,调用该函数。同时也会立即执行该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。

  apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined或者this,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,在调用时传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

function keith(a, b) { console.log(a + b); } keith.call(null, 2, 3); //5 keith.apply(null, [2, 3]); //5

应用场景:

找出数组中的最大数:Javascript中是没有提供找出数组中最大值的方法的,结合使用继承自Function.prototype的apply和Math.max方法,就可以返回数组的最大值。 将数组的空元素变为undefined:通过apply方法,利用Array构造函数将数组的空元素变成undefined。空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined和null。因此,遍历内部元素的时候,会得到不同的结果。 转换类似数组的对象:利用数组对象的slice方法,可以将一个类似数组的对象(比如arguments对象)转为真正的数组。被处理的对象必须有length属性,以及相对应的数字键。 4. Function.prototype.bind()

bind方法用于指定函数内部的this指向(执行时所在的作用域),然后返回一个新函数。bind方法并非立即执行一个函数。

5. 绑定回调函数的对象

  如果在回掉函数中使用this对象,那么this对象是会指向DOM对象,由于apply方法(或者call方法)不仅绑定函数执行时所在的对象,还会立即执行函数(而bind方法不会立即执行,注意区别),因此不得不把绑定语句写在一个函数体内。

6. .call,apply,bind方法的联系和区别

  其实用于指定函数内部的this指向的问题,这三个方法都差不多,只是存在形式上的差别。读者可以将以上的例子用三种方法尝试用三种方法实现。

第一个参数都是指定函数内部中this的指向(函数执行时所在的作用域),然后根据指定的作用域,调用该函数。 都可以在函数调用时传递参数。call,bind方法需要直接传入,而apply方法需要以数组的形式传入。 call,apply方法是在调用之后立即执行函数,而bind方法没有立即执行,需要将函数再执行一遍。有点闭包的味道。 改变this对象的指向问题不仅有call,apply,bind方法,也可以使用that变量来固定this的指向。   call,apply,bind三者的区别:call和apply方法都是在调用之后立即执行的。而bind调用之后是返回原函数,需要再调用一次才行,有点像闭包的味道,如果对闭包概念不熟悉,可以浏览这两篇文章:javascript--函数参数与闭包--详解,javascript中重要概念-闭包-深入理解。 八、 New() 操作符 创建一个空的对象  var obj=new Object(); 让空对象的原型属性指向原型链,设置原型链 obj._ proto_=Func.prototype; 让构造函数的this指向obj,并执行函数体 var result=Func.call(obj); 判断返回类型,如果是值就返回这个obj,如果是引用类型,返回这个引用对象。 九、 ES6新特性 新增了块级作用域(let,const) 提供了定义类的语法糖(class) 新增了一种基本数据类型(Symbol) 新增了变量的解构赋值 函数参数允许设置默认值,引入了rest参数,新增了箭头函数 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法 对象和数组新增了扩展运算符 ES6 新增了模块化(import/export) ES6 新增了 Set 和 Map 数据结构 ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例 ES6 新增了生成器(Generator)和遍历器(Iterator) 十、 setTimeout倒计时为什么会出现误差?

  setTimeout() 只是将事件插入了“任务队列”,必须等当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码消耗时间很长,也有可能要等很久,所以并没办法保证回调函数一定会在 setTimeout() 指定的时间执行。所以, setTimeout() 的第二个参数表示的是最少时间,并非是确切时间。

  HTML5标准规定了 setTimeout() 的第二个参数的最小值不得小于4毫秒,如果低于这个值,则默认是4毫秒。在此之前。老版本的浏览器都将最短时间设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常是间隔16毫秒执行。这时使用 requestAnimationFrame() 的效果要好于 setTimeout();

十一、原型和原型链

①原型:

  Javascript对象都有一个叫做原型的公共属性,属性名是 proto 。这个原型属性是对另一个对象的引用,通过这个原型属性我们就可以访问另一个对象所有的属性和方法。

let numArray = [1, 2, -8, 3, -4, 7];

Array对象就有一个原型属性指向Array.prototype,变量numArray继承了Array.prototype对象所有的属性和方法。

这就是为什么可以直接调用像sort()方法,也就是说

numArray.__proto__ === Array.prototype // true

当一个构造函数被创建后,实例对象会继承构造函数的原型属性,这是构造函数的一个非常重要的特性。在Javascript中使用new关键字来对构造函数进行实例化。 结论: 每一个实例对象都有一个私有属性_ proto _ ,指向它的构造函数的原型对象(prototype)。

②原型链

  每个对象拥有一个原型对象,通过 __proto__ (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.prototype.__proto__ 指向的是null)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。

  结论: 每一个实例对象都有一个私有属性 proto ,指向它的构造函数的原型对象(prototype)。原型对象也有自己的 proto ,层层向上直到一个对象的原型对象为null。这一层层原型就是原型链。

③创建对象的方法

字面量对象 let obj = {}; 构造函数创建:构造函数创建的方式更多用来在Javascript中实现继承,多态,封装等特性。 function Animal(name) { this.name = name; } let cat = new Animal('Tom'); class创建:ES6新引入的一个特性,它其实是基于原型和原型链实现的一个语法糖。 class Animal { constructor(name) { this.name = name; } } let cat = new Animal('Tom');

④扩展原型链的四种方法

构造函数创建 function Animal(name) { this.name = name; } Animal.prototype = { run() { console.log('跑步'); } } let cat = new Animal('Tom'); cat.__proto__ === Animal.prototype; // true Animal.prototype.__proto__ === Object.prototype; // true

优点: 支持目前以及所有可想象到的浏览器(IE5.5都可以使用). 这种方法非常快,非常符合标准,并且充分利用JIST优化。

缺点: 为使用此方法,这个问题中的函数必须要被初始化。另外构造函数的初始化,可能会给生成对象带来并不想要的方法和属性。

Object.create:ECMAScript 5 中引入了一个新方法: Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数: var a = {a: 1}; // a ---> Object.prototype ---> null var b = Object.create(a); b.__proto__ === a; // true

优点:  支持当前所有非微软版本或者 IE9 以上版本的浏览器。允许一次性地直接设置 __proto__ 属性,以便浏览器能更好地优化对象。同时允许通过 Object.create(null)来创建一个没有原型的对象。

缺点: 不支持 IE8 以下的版本;这个慢对象初始化在使用第二个参数的时候有可能成为一个性能黑洞,因为每个对象的描述符属性都有自己的描述对象。当以对象的格式处理成百上千的对象描述的时候,可能会造成严重的性能问题。

Object.setPrototypeOf Object.setPrototypeOf(obj, prototype) //参数 var a = { n: 1 }; var b = { m : 2 }; Object.setPrototypeOf(a, b); a.__proto__ === b; // true

优点: 支持所有现代浏览器和微软IE9+浏览器。允许动态操作对象的原型,甚至能强制给通过 Object.create(null)创建出来的没有原型的对象添加一个原型。

缺点: 这个方式表现并不好,应该被弃用;动态设置原型会干扰浏览器对原型的优化;不支持 IE8 及以下的浏览器版本。

_ proto_:使用 _proto_也可以动态设置对象的原型。 var a = { n: 1 }; var b = { m : 2 }; a.__proto__ = b; a.__proto__ === b; // true

优点: 支持所有现代非微软版本以及 IE11 以上版本的浏览器。将 __proto__ 设置为非对象的值会静默失败,并不会抛出错误。

缺点: 应该完全将其抛弃因为这个行为完全不具备性能可言;干扰浏览器对原型的优化;不支持 IE10 及以下的浏览器版本。

十二、js任务 宏任务(macrotask ):宿主(浏览器/node)发起的任务,setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI rendering 微任务(microtask ):js引擎发起的任务(微任务先于宏任务),process.nextTick、Promise、Object.observe、mutationObserve 在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

image.png 宏任务和微任务之间的关系

image.png

十三、 判断数组的方式

通过Object.prototype.toString.call()做判断

js 复制代码 Object.prototype.toString.call(obj).slice(8,-1) === 'Array';

通过原型链做判断

js 复制代码 obj.__proto__ === Array.prototype;

通过ES6的Array.isArray()做判断

js 复制代码 Array.isArrray(obj);

通过instanceof做判断

js 复制代码 obj instanceof Array


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3