牛客面试题
单选题
一、js概念与类型检测
-
利用typeof检测值的类型
- typeof检测出为number:数字或者NaN;
- typeof检测出Object:对象或者null
- typeof null == Object ;(注意:null instanceof Object 为false,因为null本身并不是一个Object类型)
-
BigInt 可以表示任意精度整数的基本数据类型,存储在栈中
-
NaN是非数字,非数字是不相同的
console.log(NaN == NaN); // false -
Symbol.for('a') 执行30次,会先检查给定的key是否已经存在,如果不存在才会新建一个值; Symbol('a')执行30次,每次都会返回不同的Symbol值
console.log( Symbol.for('a') === Symbol.for('a') ); // true console.log( Symbol('a') === Symbol('a') ); // false -
|| 真前假后:为真返回||前面的值,为假返回||后面的值
typeof [] || function; // 得到的结果是Object console.log(1 || []); // 1 而不是true console.log(true || 1); // true console.log([] && ''); // []&&假前真后:为假返回&&前面的值,为真返回&&后面的值
-
利用 “==” 判断数据是否相等:
-
如果有个操作数是布尔值,就先转为数值再比较
-
有个操作是字符串,另一个操作数是数值,会将字符串转为数值再比较
-
undefined和null除了与自身或彼此比较,和其它任何类型比较都是不等的
console.log(undefined == 0); // false console.log(undefined == false); // false console.log(undefined == undefined); // true console.log(null == null); // true console.log(null == undefined); // true
-
-
! 逻辑非:操作非布尔值类型的数据时,会先把数据转换为布尔值再取反
console.log(![] == false); // true console.log(Boolean([])); // true -
其它
console.log(Number('a')); // NaN console.log(-1 == true); // false console.log(3 + '2'); // 32
二、Math类型
-
小数转整数:
- round 四舍五入
- ceil 天花板
- floor 地板
console.log(Math.round(7.25)); // 7 console.log(Math.ceil(7.25)); // 8 console.log(Math.floor(7.25)); // 7 -
Math.random()随机生成数: [0,1)
三、字符串
-
split 将字符串分割成字符串数组,并且删除对应索引处的值
-
match 返回匹配数组
-
indexOf和search返回匹配子串的第一个位置
区别:
- indexOf查找字符串中是否有子串,顺便返回一个索引,资源消耗小,效率高
- search 是查找有某些特征的字符串,比如查找a开头的后面跟着的数字的字符串,并返回匹配的第一个索引,资源消耗大
console.log("123".split(2)); // ['1','3']
console.log('123'.match('23')); // ['23', index: 1, input: '123', groups: undefined]
console.log('123'.indexOf('23')); // 1
console.log('123'.search('23')); // 1
let text = "Mr. Blue has a blue house";
console.log(text.search(/blue/i)); // 4
四、JSON数据
-
JSON.stringify() 会输出不包含空格或者缩进的JSON字符串
let json1 = { title: "Json.stringify", author: [ "浪里行舟" ], year: 2021 }; let jsonText1 = JSON.stringify(json1); console.log(jsonText1); // {"title":"Json.stringify","author":["浪里行舟"],"year":2021} -
JSON.stringify() :第二个参数,过滤器
let json2 = { title: "json.stringify", author: ["浪里行舟" ], year: 2021, like: 'frontend', weixin: 'frontJS' }; let jsonText2 = JSON.stringify(json2, ['weixin']); console.log(jsonText2); // {"weixin":"frontJS"} const students = [ { name: 'james', score: 100, }, { name: 'jordon', score: 60, }, { name: 'kobe', score: 90, } ]; function replacer (key, value) { if (key === 'score') { if (value === 100) { return 'S'; } else if (value >= 90) { return 'A'; } else if (value >= 70) { return 'B'; } else if (value >= 50) { return 'C'; } else { return 'E'; } } return value; } // 把分数换成等级 console.log(JSON.stringify(students, replacer, 4)) // 上面的4是字符串缩进:每级缩进4格 // [ // { // "name": "james", // "score": "S" // }, // { // "name": "jordon", // "score": "C" // }, // { // "name": "kobe", // "score": "A" // } // ] -
通过JSON.stringify() 转为了 null
- 有NaN
- 有Infinity
- 有undefined
- 有function() {}
- 有symbol("")
console.log(JSON.stringify([NaN, Infinity])); // [null,null] console.log(JSON.stringify([undefined, function () { }, Symbol("")])); // '[null,null,null]' // 特殊 console.log(JSON.stringify({ x: undefined, y: function () { }, z: Symbol("") })); // '{}' -
不可枚举的属性默认会被忽略
let personObj = Object.create(null, {
name: { value: "浪里行舟", enumerable: false },
year: { value: "2021", enumerable: true },
})
console.log(JSON.stringify(personObj)) // {"year":"2021"}
五、数组
-
shift会改变原数组,返回的是number类型;filter不会改变原数组,返回的是Object类型
console.log(typeof(arr1.shift())); // number console.log(typeof(arr1.filter((item)=>item > 1))); // object -
Array.form()去重,和nums.filter()类似
let nums = [5,1,1,2,2,3,3,4,4]; console.log(Array.from(new Set(nums))); // [5, 1, 2, 3, 4] 注意必须要转为set类型,否则无效 nums = [5,1,1,2,2,3,3,4,4]; console.log( nums.filter((n, i) => { return nums.indexOf(n) === i // [5, 1, 2, 3, 4] }) );
六、正则表达式
-
做一个匹配首字符是非数字的,后面的字符是数字的正则表达式
const reg = /^d+[^d]+/ console.log(reg.test(1234)); // false console.log(reg.test('123a')); // false console.log(reg.test('d1231')); // true console.log(reg.test('a123bde')); // false -
字符串的search和正则表达式的test的区别:search匹配到符合规格的数据,返回索引;test匹配到符合规格的数据,返回布尔值
const reg = new RegExp(2); console.log(reg.test('123')); // true console.log("123".search(reg)); // 1 console.log("345".search(reg)); // -1 -
使用reg.exec()拿到匹配结果
const reg1 = /\d/ console.log(reg1.exec("qwe1234rty")); // ['1', index: 3, input: 'qwe1234rty', groups: undefined] console.log(reg1.exec("qwerty")); // null -
str.match(reg)和reg.exec(str)都相同,只要匹配到符合规格的数据就返回数组,否则就返回null
console.log("qwe1234rty".match(reg1)); // ['1', index: 3, input: 'qwe1234rty', groups: undefined] console.log("qwerty".match(reg1)); // null
七、其它
-
编写高性能javascript
- 遵循严格模式:"use strict"
- 将js脚本成组打包,减少请求,尽量减少使用闭包
- 使用非堵塞方式下载js脚本,最小化重绘和回流
-
请写出格式化日期格式为2023-12-15的函数
let datatime = new Date(); let formData = function(datatime) { let year = datatime.getFullYear(); let month = datatime.getMonth() + 1; let data = datatime.getData(); month = month < 10 ? '0' + month : month; data = data < 10 ? '0' + data : data; return year + '-' + month + '-' + data; } -
如何最小化重绘repaint和回流reflow
- 对元素进行复杂操作时,先隐藏(display:none),操作完再显示
- 需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document
- 尽量避免用table布局(table元素一旦触发回流就会导致table里所有的其它元素回流)
-
js原型:使用Object.create(null)没有原型
let emobi = Object.create(null) console.log(emobi); // 没有原型 console.log([]); // 有原型 console.log({}); // 有原型 -
DOM
-
不支持冒泡的鼠标事件:onmouseleave、onmouseenter
-
阻止默认事件的默认操作的方法是:preventDefault
-
事件传播的三个阶段:捕获 ——> 目标 ——> 冒泡
-
onblur:失去焦点 | onfocus 获得焦点
-
添加子节点
parentNode.appendChild(newNode) -
获取地理位置所在的经纬度
console.log(Geolocation.getPosition());
-
-
箭头函数
- 箭头函数没有原型属性
- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
- 箭头函数不绑定arguments,取而代之用rest参数解决
- 箭头函数不能用new创建一个箭头函数实例
-
Promise
-
在所有的 Promise 对象都成功解决后才会将一个成功的结果数组作为解决值返回,如果有一个 Promise 对象被拒绝了,则会立即将一个拒绝原因作为拒绝值返回。
let p1 = Promise.resolve('aaa') let p2 = Promise.resolve('bbb') let p3 = Promise.reject('ccc') let p4 = Promise.resolve('ddd') Promise.all([p1, p2,p3, p4]).then(res => { console.log(res); //如果没有p3,即整个promise成功,那么返回数组 }).catch(err => { console.log('error',err); // ccc 一旦返回数据失败,就停止并输出失败数据 }) // let p5 = Promise.reject('aaa') let p6 = Promise.resolve('bbb') // let p7 = Promise.reject('ccc') let p8 = Promise.resolve('ddd') Promise.race([ p6, p8]).then(res => { console.log('成功', res); //bbb 如果没有reject,返回第一个请求结果 }).catch(err => { console.log('失败', err); // aaa 一旦返回数据失败,就停止并输出失败数据 }) -
Promise.all() 适用于当我们需要等待多个异步操作全部完成,然后再进行下一步操作的场景。
- 例如,我们需要从多个 API 请求中获取数据,然后将这些数据合并成一个结果
- 在这种情况下,我们可以使用 Promise.all() 来等待所有的请求都完成,然后将结果合并起来。
-
Promise.race() 适用于当我们需要等待多个异步操作中的其中一个完成,然后再进行下一步操作的场景。
- 例如,我们需要从多个 API 请求中获取数据,但只需要获取其中一个请求的结果即可。
- 在这种情况下,我们可以使用 Promise.race() 来等待其中一个请求完成,然后处理其结果。
-
-
结构赋值
let [zz,xx,cc,vv,bb] = [1,2,3,4,5]; console.log([zz,xx,cc,vv,bb] );
多选题
一、js基础
-
ox十六进制;1e2 表示10的2次方乘以1
let aa = 0xa1; // 161 a1对应的二进制为10100001,十进制为161 // aa = 076; 报错 // aa = 0b21 报错 aa = 7e2; // 700 console.log(aa); console.log(typeof 17n); // bigint -
isNaN判断
console.log(NaN === 'number'); // false console.log(isNaN('abc')); // true console.log(isNaN(NaN)); // true
二、ES6
-
let和const都不存在变量提升,存在暂时性死区,只能在声明位置后面使用
a = "a" // 报错 let a -
Promise.then的回调函数中,可以返回一个新的Promise
三、DOM
-
设置元素样式
document.body.style.fontSize = '16px' document.body.style.setProperty('background-color','#fff') document.body.style = 'background-color: #fff' // document.body.style.['background-color'] = '#fff' -
阻止冒泡事件的三种方法
event.cancelable = true event.stopPropagation(); return false; // 该方法只能阻止默认事件,比如点击连接跳转,但不能阻止冒泡事件发生 event.preventDefault();
填空题
一、类型检测
-
typeof检测的结果是string类型
var x = typeof x var res = typeof typeof x console.log(x,res); // undefined,string console.log(typeof typeof 0); // string -
使用Object.prototype.toString.call(type) ,返回一个数组,分别为type的类型和它的具体类型
var ar1 = [] console.log(typeof ar1,Object.prototype.toString.call(ar1)); // object [object Array] console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
二、类型转换
-
字符串+数字 是拼接方式,无论先后位置
console.log("2"+3+4) // 234 console.log('5'+3,5+'3') // 53 , 53 -
parseInt的参数为1个,有多个参数时返回NaN
console.log(parseInt("111办公室")) // 111 console.log(parseInt('77',40)); // NaN -
使用map返回数组
console.log(["0x1","0x2","0x3"].map(parseInt)); // [1, NaN, 0] console.log([2,3,4].map((item)=>item > 2)); // [false,true,true]
三、逻辑判断
-
连续数值的大小比较,可以用||拆分开
console.log([5<6<3,3<2<4]); // [true,true] console.log(5<6 || 6<3); // true console.log(3<2 || 2<4); // true // &&优先级高于|| console.log(true||false&&false, true&&false||true) // true true -
有多个 + - 符号连接的两个数,看作为正负号
console.log(1 + - - - - + + - + - - 1 ); // 0 后面的1的前面的+ - 都看作是正负符号
程序题
一、js基础
-
new String(c); 拿到的是一个对象
console.log(new String('A')); // {'A'} console.log((new String('A')) instanceof Object);// true -
修改字符串的长度不会修改掉字符串本身
let strq = "我非常喜欢编程"; strq.length = 3; console.log(strq); // 我非常喜欢编程 -
非严格模式下,函数的arguments数组里面的内容会随着函数内赋值而变化;而严格模式下,arguments不会随着函数内赋值而改变
function side(arr) { arr[0] = arr[2]; } function func1(a, b, c = 3) { // 有参数的赋值,属于严格模式 c = 10; side(arguments); console.log(arguments); // [1,1,1,...] console.log(a + b + c); } function func2(a, b, c) { c = 10; side(arguments); console.log(arguments); // [10,1,10,...] console.log(a + b + c); } func1(1, 1, 1); // 12 func2(1, 1, 1); // 21 -
使用new Number(x) 同样拿到的是一个对象Object
var g = 3; var h = new Number(3); var j = 3; console.log(h instanceof Object); // true console.log(g == h); // true console.log(g === h); // false console.log(h === j); // false -
arr.shift()去掉首项元素 ; arr.splice(0,1,3) 从索引0开始删除一个元素并在末尾加上一个元素3
-
小数之和会有误差
console.log(0.1 + 0.2); // 0.30000000000000004 -
其它
console.log(typeof Function); // function console.log(typeof Object); // function console.log(typeof {}); // objectqita console.log('lk' + 1); // lk1 console.log('lk' - 1); // NaN console.log(Function instanceof Object); // true -
class属于function类型
console.log(typeof function () {} ); // function console.log(typeof class {}); // function console.log(Object.prototype.toString.call(class{})); // [object Function] -
剩余参数语法:... 将所有参数都收集到一个数组中
function getAge(...args) { console.log(typeof args); // ...args剩余参数语法,将所有参数都收集到一个数组中 // console.log(args instanceof Array); // true } getAge(21); // object
二、js深入
-
对this的判断
var xxx = 1; var OBJ = { xxx: 3, fun:function () { var xxx = 5; return this.xxx; } }; var fun = OBJ.fun; console.log( OBJ.fun(), fun() ); // 3 1 // 第一次this指向OBJ这个对象 第二次this指向全局window -
对箭头函数中this的理解
function NEWfun () { return () => { return () => { return () => { console.log(this.name) } } } } var foo = NEWfun.call({name: 'foo'}) var t1 = foo.call({name: 'bar'})()() // foo var t2 = foo().call({name: 'baz'})() // foo var t3 = foo()().call({name: 'qux'}) // foo // 箭头函数的this是继承父级作用域的this,不是指向调用者 // 第一句:把this指向{name:'foo'} // 之后的句子返回的都是箭头函数,使用call()不能改变this指向,作用域链上的this还是指向{name:'foo'} var jb = 2; let jbo = { jb: 1, foo: () => { console.log(this.jb) } } const jbo1 = jbo.foo // log1 jbo.foo() // 2 // log2 jbo1() // 2 // 箭头函数始终指向外面 -
构造函数的默认参数
const Persont = function(name="wang",age=10) { this.name = name; this.age = age; return this.name +' is '+ this.age + 'years old' } let pers = new Persont('zhang',11) console.log(pers) // Persont {name: 'zhang', age: 11} -
new绑定优先级高于bind绑定
var global = 'global'; var obj5 = { global: 'local', foo1: function(){ this.global = 'foo'; }.bind(window) // 由于new绑定优先级大于bind绑定,所以此时函数内部的this还是obj5 } var bar = new obj5.foo1(); setTimeout(function() { console.log(window.global); // global }, 0); console.log(bar.global); // foo var bar3 = bar2 = bar; bar2.global = 'foo2'; console.log(bar3.global); // foo2
三、js事件循环
-
promise.then()方法,只有在resolve返回结果之后才会参数,如果.then().then()中第一个then没有返回结果,那么第二个then拿到的参数为undefined
const promiseA = Promise.resolve('a') promiseA.then((res) => { console.log(res) // a }).then((res) => { console.log(res) // undefined }) // 第二个then中的回调函数会接收前一个'then'返回的结果 const promiseB = Promise.resolve('b') promiseB.then((res) => { console.log(res) // b }) promiseB.then((res) => { console.log(res) // b }) -
事件循环:先处理执行栈中的事件,再处理任务队列中的事件:这期间微任务要比宏任务先执行
微任务:
- promise.then()
- proness.nextTick(Node环境)
- MutationObserver(监听DOM树变化)
宏任务:
- script整体代码;
- setTimeout,setInterval;
- UI渲染;
- I/O,setImmediate(Node环境)
setTimeout(() => { console.log(1) }, 0) console.log(-1) const P = new Promise((resolve, reject) => { console.log(2) // 这里等同于log-1 setTimeout(() => { resolve() console.log(3) }, 500) }) P.then(() => { console.log(4) // 等P执行完毕后才会执行 }) console.log(5) // -1 2 5 1 3 4 -
无论宏任务在哪里,都是在微任务之后执行
(async () => { console.log(1); setTimeout(() => { console.log(2); }, 0); await new Promise((resolve, reject) => { console.log(3); // resolve(); 只有在resolve执行完毕,才会输出4 5 }).then(() => { console.log(4); }); console.log(5); })(); -
promise还没有执行回调then() 函数的时候,会和执行栈中的事件同步操作
new Promise((resolve) => { // new Promise执行是同步的 console.log('1') resolve() console.log('2') }).then(() => { console.log('3') }) setTimeout(() => { console.log('4') }) console.log('5') // 1 2 5 3 4 -
冒泡事件:会先执行微任务(包括父元素事件)完毕后,再执行宏任务
<body> <div class="outer"> <div class="inner">hahah</div> </div> <script> var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); function onClick() { console.log('click'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise'); }); } // click promise click promise timeout timeout // 冒泡事件 inner.addEventListener('click', onClick); outer.addEventListener('click', onClick); </script>
四、ES6
-
给对象增加属性的函数:Object.defineProperty()
-
拿到对象的key值:Object.key(obj)
const student = {name: 'ZhangSan'} Object.defineProperty(student, 'age', {value: 22}) console.log(student) // {name: "ZhangSan";age: 22} console.log(Object.keys(student)) // ["name"] -
generator
function * cb(x, y) { for(let i = Math.ceil(x); i <= y; i++) { yield i; } } var a = cb(6, 9); console.log(a.next()); // {value: 6, done: false} console.log(a.next()); // {value: 7, done: false} // 当a到9以后,done=true // 1.函数生成器特点:function * fun(){} // 2.调用函数生成控制器 // 3.通过next()方法执行函数 // 4.遇到yield函数暂停 // 5.next()继续执行 // 6.使用let每个i都生成一个作用域,互补干扰,保留当时的赋值 -
扩展运算符 ...args
function fn(...args) { console.log(typeof args); } fn(21); // Object -
class使用ES6代码编译
// class class Person { constructor (name) { this.name = name; } greet () { console.log(`Hi, my name is ${this.name}`); } greetDelay (time) { setTimeout(() => { console.log(`Hi, my name is ${this.name}`); }, time); } } // ES6代码编译后所生成的ES5代码 function Person1(name) { this.name = name } Person1.prototype = { greet: function() { console.log('Hi, my name is '+this.name); }, greetDelay: function(time) { var that = this setTimeout(function() { console.log("Hi, my name is "+that.name); },time) } } -
使用标签模板
function getPersonInfo(one, two, three) { console.log(one) console.log(two) console.log(three) } const person = 'Lydia' const age = 21 getPersonInfo `${person} is ${age} years old` // Lydia 21
五、js作用域
-
let、const块级作用域
- 不存在变量提升
- 存在暂时性死区问题
function sayHello() { console.log(name); console.log(age); var name = "Tom"; let age = 18; } sayHello(); // undefined 报错 -
用var声明的变量,在全局范围内有效; 用let声明的变量,每次都会新声明一个作用域
for (var i = 0; i < 3; i++) { setTimeout(_ => { console.log(i) }) } // 3 3 3 // 全局中只有一个变量i,每次循环时,setTimeOut定时器里指的是全局变量i,而循环里的setTimeOut是在循环结束后才执行,所以输出3个3 for (let i = 0; i < 3; i++) { setTimeout(_ => { console.log(i) }) } // 0 1 2 -
对象1中的对象2中的this指向对象2
"use strict" var name = 'Jay' var person = { name: 'Wang', pro: { name: 'Michael', getName: function () { console.log(this.name); return this.name } } } person.pro.getName() // Michael this指向了{name: 'Michael', getName: ƒ} var people = person.pro.getName people() // Jay -
变量声明都会被提到作用域顶部,但是赋值仍然在其原来的位置
compute(10,100); // 220 var compute = function(A,B) { console.info(A * B) ; }; function compute(A,B){ console.info(A + B); } function compute(A,B){ console.info((A + B)*2); } compute(2,10); // 20 meili() // meili function meili() { console.log("meili") } mogu() // mogu is not a function var mogu = function() { console.log("mogu") }
六、js原型链
-
在实例中没有找到属性,就去原型函数上找
function Fn1(name) { if(name) { this.name = name; } } Fn1.prototype.name="jack" let a = new Fn1(); console.log('a:', a.name); // a:jack function Fn2(name) { this.name = name; } Fn2.prototype.name="jack" let b = new Fn2(); console.log('b:', b.name); // b:undefined // 定义实例a,没有传入参数,判断name为false,所以无法添加name属性,所以会沿着原型链去找,于是打印了Fn1.prototype.name // 定义实例b,没有传入参数,会给this.a赋值为undefiend,b上面就有了name属性,打印b.name -
原型中的变量是全局变量
var Foo = (function() { var x = 0; function Foo() {} Foo.prototype.increment = function() { ++x; console.log(x); }; return Foo; })(); var a = new Foo(); a.increment(); // 1 a.increment(); // 2 var b = new Foo(); b.increment(); // 3 // 注意到Foo()本身已经立即执行了,所以x变量是会跟着改变的 // increment 函数可以访问到 x 变量 -
非严格模式下,函数的this指向window
var name = 'Jay' function Person(name){ this.name = name; console.log(this.name) } var a = Person('Tom') // Tom console.log(name) // Tom console.log(a) // undefined var b = new Person('Michael') // Michael console.log(b) // Person {name:'Michael'} // 因为Person('Tom')默认绑定,非严格模式下this指向window,修改全局name='Tom' // Person('Tom')是没有返回值的,所以为undefined // 此时this指向b,参数为Michael -
实例._ proto_ = 构造函数.prototype
var tmp = {}; var A = function() {}; A.prototype = tmp; var a = new A(); A.prototype = {}; var b = Object.create(tmp); b.constructor = A.constructor; console.log(a instanceof A); console.log(b instanceof A); console.log(b); // false // false // var a = new A()时,a.__proto__指向A.prototype,即tmp // A.prototype = {},此时A指向了新的 空对象 {} // b.prototype 指向tmp // A和b的构造函数相同,但是原型不同 -
继承关系
class A{} class B extends A{} const a = new A() const b = new B() console.log(a.__proto__); // constructor: class A console.log(b.__proto__); // constructor: class B console.log(B.__proto__); // class A{} console.log(B.prototype.__proto__); // constructor: class A console.log(b.prototype.__proto__); // 报错
七、其它
-
computed有缓存功能,当其数据不变时,就不会进行操作
var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 有缓存功能,message只更新一次,所以1只输出一次 test: function () { console.log(1) return this.message } }, created: function (){ this.message = 'World' for (var i = 0; i < 5; i++) { console.log(this.test) } } })