js面试题

1  js冒泡排序,快速排序,时间空间复杂度

var arr = [3, 10, 6, 2];
// 遍历数组
for (var i = 0; i < arr.length - 1; i++) {
    // 这里要根据外层for循环的 i ,逐渐减少内层 for 循环的次数
    for (var j = 0; j < arr.length - 1 - i; j++) {
        if (arr[j] > arr[j + 1]) {
            var num = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = num;
        }
    }
}
console.log(arr)  // [ 2, 3, 6, 10 ]

时间复杂度
最好的情况:数组本身是顺序的,外层循环遍历一次就完成O(n)
最坏的情况:,O(n2)数组本身是逆序的,内外层遍历O(n2)
空间复杂度
开辟一个空间交换顺序O(1)

快速排序
时间复杂度
最好的情况:每一次base值都刚好平分整个数组,O(nlogn)
最坏的情况:每一次base值都是数组中的最大/最小值,O(n2)
空间复杂度
快速排序是递归的,需要借助栈来保存每一层递归的调用信息,所以空间复杂度和递归树的深度一致
最好的情况:每一次base值都刚好平分整个数组,递归树的深度O(logn)
最坏的情况:每一次base值都是数组中的最大/最小值,递归树的深度O(n)

function quicksort(arr, left, right) {
  if (left > right) {
    return;
  }
  var i = left,
    j = right,
    base = arr[left]; //基准总是取序列开头的元素
  //   var [base, i, j] = [arr[left], left, right]; //以left指针元素为base
  while (i != j) {
    //i=j,两个指针相遇时,一次排序完成,跳出循环
    // 因为每次大循环里面的操作都会改变i和j的值,所以每次循环/操作前都要判断是否满足i<j
    while (i < j && arr[j] >= base) {
      //寻找小于base的右指针元素a,跳出循环,否则左移一位
      j--;
    }
    while (i < j && arr[i] <= base) {
      //寻找大于base的左指针元素b,跳出循环,否则右移一位
      i++;
    }
    if (i < j) {
      [arr[i], arr[j]] = [arr[j], arr[i]]; //交换a和b
    }
  }
  [arr[left], arr[j]] = [arr[j], arr[left]]; //交换相遇位置元素和base,base归位
  //   let k = i;
  quicksort(arr, left, i - 1); //对base左边的元素递归排序
  quicksort(arr, i + 1, right); //对base右边的元素递归排序
  return arr;
}

 2  js常用函数

(1)alert函数:显示一个警告对话框,包括一个OK按钮。
(2)confirm函数:显示一个确认对话框,包括OK、Cancel按钮。
(3)escape函数:将字符转换成Unicode码。
(4)eval函数:计算表达式的结果。
(5)isNaN函数:测试是(true)否(false)不是一个数字。
(6)parseFloat函数:将字符串转换成符点数字形式。
(7)parseInt函数:将符串转换成整数数字形式(可指定几进制)。
(8)prompt函数:显示一个输入对话框,提示等待用户输入。例如:

(1)join函数:转换并连接数组中的所有元素为一个字符串

(2)length函数:返回数组的长度。

(3)reverse函数:将数组元素顺序颠倒。

(4)sort函数:将数组元素重新排序。

(1)getDate函数:返回日期的"日"部分

3  js字符串反转

return str == str.split('').reverse().join('');

4  es6新特性及其详解

箭头函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};
  1. 箭头函数this是创建这个函数的作用域的this,把父作用域的this当作自己的this,
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 箭头函数没有自己的this,arguments、super、new.target,prototype

  4. call()或者apply()调用箭头函数时,无法对this进行绑定,
  5. 当传多个值的时候需要加括号
  6. 当省略{}和return时,如果返回的内容是一个对象,对象需要用括号()括起来

promise

promise有三个状态:
1、pending[待定]初始状态
2、fulfilled[实现]操作成功
3、rejected[被否决]操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
promise状态一经改变,不会再变。

promise作为队列最为重要的特性,我们在任何一个地方生成了一个promise队列之后,我们可以把他作为一个变量传递到其他地方。任何地方使用

.then()

1、接收两个函数作为参数,分别代表fulfilled(成功)和rejected(失败)
2、.then()返回一个新的Promise实例,所以它可以链式调用
3、当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
4、状态响应函数可以返回新的promise,或其他值,不返回值也可以我们可以认为它返回了一个null;
5、如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
6、如果返回其他任何值,则会立即执行下一级.then()

.then()里面有.then()的情况

1、因为.then()返回的还是Promise实例
2、会等里面的then()执行完,再执行外面的

Promise.all() 批量执行

Promise.all([p1, p2, p3])用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise
它接收一个数组作为参数
数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变
当所有的子Promise都完成,该Promise完成,返回值是全部值得数组
有任何一个失败,该Promise失败,返回值是第一个失败的子Promise结果

Promise.race() 类似于Promise.all() ,区别在于它有任意一个完成就算完成

let,const

let,没有变量提升,块级作用域,不能重复声明

const没有变量提升,块级作用域,必须声明不能改值

模板字符串

反引号,${}设置变量

for-in和for-of

for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

for-in总是得到对象的key或数组、字符串的下标。

for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。

5 this指向的区别,闭包中this指向

跨作用域访问函数变量

上级作用域无法直接访问下级作用域中的变量,但下级作用域可以访问上机作用域中的变量。

  • 让外界读取某个函数内部的变量

  • 让这些变量始终保持在内存中(可能会造成内存泄漏)

  • 最基本的作用:可以通过闭包返回的函数或者方法,来修改函数内部的数据

  • 创建一个私有的空间,保护数据

this对象是在运行时基于函数的执行环境绑定的,谁调用this就是谁。如果在全局范围中,this就是window,如果在对象内部,this就指向这个对象。

call、apply、bind三者为改变this指向的方法。

共同点:第一个参数都为改变this的指针。若第一参数为null/undefined,this默认指向window

call(无数个参数)

  • 第一个参数:改变this指向
  • 第二个参数:实参
  • 使用之后会自动执行该函数

apply(两个参数)

  • 第一个参数:改变this指向
  • 第二个参数:数组(里面为实参)
  • 使用时候会自动执行函数

bind(无数个参数)

  • 第一个参数:改变this指向
  • 第二个参数之后:实参
  • 返回值为一个新的函数
  • 使用的时候需要手动调用下返回 的新函数(不会自动执行)

call、apply与bind区别:前两个可以自动执行,bind不会自动执行,需要手动调用

call、bind与apply区别:前两个都有无数个参数,apply只有两个参数,而且第二个参数为数组

7  js代码正确放置位置

按照需求,需要提前加载的放在head里,不会第一时间展示的放在底部

将 JavaScript 代码放置于 HTML 文档的 <head></head> 标签之间是一个通常的做法。

8  js防抖和节流以及实现方式

  • 函数节流: 指定时间间隔内只会执行一次任务;适合大量事件按时间做平均分配触发。
  • 函数防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。适合多次事件一次响应的情况

函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用来节流。

//节流throttle代码:
function throttle(fn,delay) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
         // 在函数开头判断标记是否为true,不为true则return
        if (!canRun) return;
         // 立即设置为false
        canRun = false;
        // 将外部传入的函数的执行放在setTimeout中
        setTimeout(() => { 
        // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
        // 当定时器没有执行的时候标记永远是false,在开头被return掉
            fn.apply(this, arguments);
            canRun = true;
        }, delay);
    };
}
 
function sayHi(e) {
    console.log('节流:', e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi,500));

函数防抖就是对于一定时间段的连续的函数调用,只让其执行一次。

//防抖debounce代码:
function debounce(fn,delay) {
    var timeout = null; // 创建一个标记用来存放定时器的返回值
    return function (e) {
        // 每当用户输入的时候把前一个 setTimeout clear 掉
        clearTimeout(timeout); 
        // 然后又创建一个新的 setTimeout, 这样就能保证interval 间隔内如果时间持续触发,就不会执行 fn 函数
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, delay);
    };
}
// 处理函数
function handle() {
    console.log('防抖:', Math.random());
}
        
//滚动事件
window.addEventListener('scroll', debounce(handle,500));

9  js延迟加载方法

  1. defer 属性
  2. async 属性
  1. 动态创建DOM方式
  2. 使用jQuery的getScript ()方法
  1. 使用setTimeout延迟方法的加载时间
  2. 让JS最后加载

10  docunment.write和innerHtml的区别

document.write是直接将内容写入页面的内容流,会导致页面全部重绘,

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

11  原型,构造函数,和实例的关系

所谓的构造函数其实就是一个普通的函数前面加了new运算符,其实质也是一个函数,所以构造函数都有函数的prototype属性。

实例就是通过构造函数创建出来的对象。

原型指的就是原型对象,至于是谁的原型对象,需要靠函数的prototype属性和实例的__proto__属性来区别。

 指从一个实例对象开始往上找,这个实例对象的__proto__属性所指向的则是这个实例对象的原型对象,如果用obj表示这个实例,则原型对象表示为obj.__proto__。同时,这个原型对象顾名思义也是一个对象,而且它也有上一级的原型对象,相对于上一级原型对象而言,它也是一个实例对象,那么它也拥有__proto__属性,它的__proto__属性也指向它的原型对象,后面也以此类推,一直到Object.prototype这个原型为止,Object.prototype为原型链的末尾点。

构造函数通过其属性prototype去寻找它关联的原型,如果用M表示构造函数,M.prototype所指的就是它关联的原型对象,而原型对象可以通过构造器constructor来寻找与自身关联的构造函数,所以就有M.prototype.conctructor===M。

https://blog.youkuaiyun.com/yin_991/article/details/80954453

12  简述proto protype,object.getprotypeof的区别

Object.prototype.proto === null

只有函数Function对象Object有prototype属性,而实例对象没有prototype属性,但它们的实例对象有__proto__属性,并且它们(prototype、_proto_)指向同一个原型对象,即它们是相等的 (指向同一地址)。

13  对象深拷贝和浅拷贝的区别和实现方式

浅拷贝将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用(拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响)

深拷贝是将原对象的各个属性的“值”逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深拷贝的方法递归复制到新对象上(注意拷贝的“值”而不是“引用”)

var newObj = JSON.parse(JSON.stringify(obj));
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
	if (obj.hasOwnProperty(key)) {
	    newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。

14   简述macrotask和microtask的区别

marcotask 的本质是浏览器多个线程之间通信的一个消息队列

microtask 是确确实实存在的一个队列,microtask 是属于当前线程的,而不是其他线程 postTask 过来的任务

宏任务队列可以有多个,微任务队列只有一个;

js执行时有两个异步队列:宏队列微队列。优先执行微队列中的任务,而且每次执行完宏队列中的任务后,都会查看微队列中是否有任务,假如有任务则先执行微队列中的任务,再执行宏队列中的任务。例外:浏览器会先执行一个宏任务——script

  • 宏队列存储的是:scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering、ajax回调
  • 微队列存储的是:promisemutationprocess.nextTickObject.observeMutationObserver

15  v8的内存分代和回收算法

Node程序运行中,此进程占用的所有内存称为常驻内存(Resident Set)。

常驻内存由以下部分组成:

代码区(Code Segment):存放即将执行的代码片段

栈(Stack):存放局部变量

堆(Heap):存放对象、闭包上下文

堆外内存:不通过V8分配,也不受V8管理。Buffer对象的数据就存放于此。

除堆外内存,其余部分均由V8管理。

栈(Stack)的分配与回收非常直接,当程序离开某作用域后,其栈指针下移(回退),整个作用域的局部变量都会出栈,内存收回。

最复杂的部分是堆(Heap)的管理,V8使用垃圾回收机制进行堆的内存管理,也是开发中可能造成内存泄漏的部分,是程序员的关注点。

V8将堆中的对象分为两类:

新生代:年轻的新对象,未经历垃圾回收或仅经历过一次

在V8引擎的内存结构中,新生代主要用于存放存活时间较短的对象。新生代内存是由两个 semispace(半空间) 构成的,内存最大值在 64 位系统和 32 位系统上分别为 32MB 和 16MB ,在新生代的垃圾回收过程中主要采用了 Scavenge 算法。

Scavenge 算法是一种典型的牺牲空间换取时间的算法,对于老生代内存来说,可能会存储大量对象,如果在老生代中使用这种算法,势必会造成内存资源的浪费,但是在新生代内存中,大部分对象的生命周期较短,在时间效率上表现可观,所以还是比较适合这种算法。

老年代:存活时间长的老对象,经历过一次或更多次垃圾回收的对象

16 甚麽是闭包

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包;
通常一个函数运行结束之后,内部变量就会被销毁释放,C就是这样;然而在闭包这种结构中,外部函数的局部变量在该函数运行之后,其内部变量值在竟然在其返回的内部函数中得到了保持,这是比较本质的内容!!

闭包的概念:
1)函数嵌套
2)内部函数使用外部函数的变量
3)外部函数的返回值为内部函数

闭包典型应用,装饰器->传入的参数都因此结构滞留在返回的函数中!滞留了传入的函数就变成该函数的装饰器。

17  js面向对象编程

Everything is object (万物皆对象)

面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维 护性。

面向对象的特性:

  • 封装性
  • 继承性
  • [多态性]抽象

构造函数,工厂函数,原型链指针都是面向对象

18 正则表达式匹配

Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

手机号^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

身份证号(15位、18位数字):^\d{15}|\d{18}$

帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$

日期格式:^\d{4}-\d{1,2}-\d{1,2}

19  document.ready和window.onload的区别

ready事件在DOM结构绘制完成之后就会执行,这样能确保就算有大量的媒体文件没加载出来,JS代码一样可以执行。

  load事件必须等到网页中所有内容全部加载完毕之后才被执行。如果一个网页中有大量的图片的话,则就会出现这种情况:网页文档已经呈现出来,但由于网页数据还没有完全加载完毕,导致load事件不能够即时被触发。

ready先执行

20  浅拷贝和赋值

先看赋值,将一个对象赋值给一个新的对象的时候,赋的其实是该对象在栈中的地址,而不是堆中的数据。 也就是一个对象的改变就会改变另外一个对象。

再看浅拷贝,浅拷贝会创建一个对象,再去遍历原始对象,如果原对象的属性值是基础类型,那么就拷贝值,如果是引用类型,则拷贝的是指针。

21 网站加载很慢

减少http请求,使用cdn,压缩组件,样式表放头部,js放底部,减少dom操作(减少回流与重绘),引用外部jscss,减少dns查找,精简代码,避免重定向,使用缓存

22 终止异步操作

about

23  js懒加载

先给img设置一个自定义属性用来存放图片地址
然后判断 图片是否处在可视区域 在可视区域便设置scr属性

var liList = document.querySelectorAll("li");
      var lT = document.documentElement.clientHeight;
      function load() {
            for (var i = 0; i < liList.length; i++) {
            var jQ = document.documentElement.scrollTop;
			// 判断:这里是核心重点区域 图片到定位父元素的距离 小于 可视窗口 + 卷曲高度 
			//成立就加载src
            if (liList[i].offsetTop < jQ + lT) {
                liList[i].children[0].src = liList[i].children[0].getAttribute("_src");
                  }
                        }
                }

           window.onscroll = load
          window.onload = load

JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

解决回调地狱(异步操作)

promise,generator,async/awite

generator(生成器),generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。

function* foo(x) {
    yield x + 1;
}

调用generator对象有两个方法,一是不断地调用generator对象的next()方法:

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果donetrue,则value就是return的返回值。

当执行到donetrue时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。

第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done

1 yield 表达式只能用在 Generator 函数里面,用在其它地方都会报错

(2)、yield 表达式如果用在另一个表达式中,必须放在圆括号里面

(3)、yield 表达式用作参数或放在赋值表达式的右边,可以不加括号

JS中的垃圾回收机制

标记清除、计数引用。

这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。

另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减1,当这个值的引用次数为0的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为0的这些值。

用引用计数法会存在内存泄露,

==和===、以及Object.is的区别

当进行双等号比较时候: 先检查两个操作数数据类型,如果相同, 则进行===比较, 如果不同, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较, 而===比较时, 如果类型不同,直接就是false.

主要的区别就是+0!=-0 而NaN==NaN
(相对比===和==的改进)

setTimeout 和 setInterval 的不同之处

setTimeout() 主要包含两个参数,第一个参数就是定时器需要定时执行的函数,该参数可以是一个用函数名表示的函数调用语句,也可以是一个函数定义语句;第二个参数是一个单位为毫秒的数值(表示执行第一个参数指定操作所需的等待时间)。该方法表示经过第二个参数所设定的时间后,执行一次第一个参数指定的操作。setTimeout() 执行后同样会返回一个唯一的数值 ID。

setTimeout 和 setInterval 的不同之处在于:setInterval 可以循环不断地执行指定操作,而 setTimeout 只能执行一次参数指定的操作。不过,通过对 setTimeout() 的递归调用,可以让 setTimout() 达到与 setInterval() 同样的循环不断执行操作的目的。

● JS实现跨域

JSONP:通过动态创建script,再请求一个带参网址实现跨域通信。document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

location.hash + iframe跨域:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

window.name + iframe跨域:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。

postMessage跨域:可以跨域操作的window属性之一。

CORS:服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求,前后端都需要设置。

代理跨域:启一个代理服务器,实现数据的转发

● Js基本数据类型

基本数据类型:undefined、null、number、boolean、string、symbol

js去重

set,indexof,includes,for,filter

 怎么获得对象上的属性:比如说通过Object.key()

参考回答:

从ES5开始,有三种方法可以列出对象的属性

for(let I in obj)该方法依次访问一个对象及其原型链中所有可枚举的类型

object.keys:返回一个数组,包括所有可枚举的属性名称

object.getOwnPropertyNames:返回一个数组包含不可枚举的属性

● 了解事件代理吗,这样做有什么好处

参考回答:

事件代理/事件委托:利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的事件,

简而言之:事件代理就是说我们将事件添加到本来要添加的事件的父节点,将事件委托给父节点来触发处理函数,这通常会使用在大量的同级元素需要添加同一类事件的时候,比如一个动态的非常多的列表,需要为每个列表项都添加点击事件,这时就可以使用事件代理,通过判断e.target.nodeName来判断发生的具体元素,这样做的好处是减少事件绑定,同事动态的DOM结构任然可以监听,事件代理发生在冒泡阶段

Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源

window.onload = function(){
            var oBox = document.getElementById("box");
            oBox.onclick = function (ev) {
                var ev = ev || window.event;
                var target = ev.target || ev.srcElement;
                if(target.nodeName.toLocaleLowerCase() == 'input'){
                    switch(target.id){
                        case 'add' :
                            alert('添加');
                            break;
                        case 'remove' :
                            alert('删除');
                            break;
                        case 'move' :
                            alert('移动');
                            break;
                        case 'select' :
                            alert('选择');
                            break;
                    }
                }
            }
            
        }

js多页面通信

storage,webwork

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

https://blog.youkuaiyun.com/huakaiwuxing/article/details/78968642

 给两个构造函数A和B,如何实现A继承B?

参考回答:

1

2

3

function A(...) {}  A.prototype...

function B(...) {}  B.prototype...

A.prototype = Object.create(B.prototype);

● 知道private和public吗

参考回答:

public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用

private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值