综合面试题-js基础
call和apply的区别是什么,哪个性能更好
都是用来改变this指向的,区别是传参
call是一个一个逗号分隔传参,apply是以一个数组的形式去传参,所有参数以一个数组的形式传递进去
3个以内,两者差不多
超过3个,call比apply性能好一些
其他相关:都是function原型上的方法,供每个function实例调用。
bind并没有立即执行,而是预先处理保存了参数信息,改变this。主动引出bind概念,主动引出原型和原型链、实例和类。
这样最多也只能精确到毫秒,有更专业的方法
console.time('START')
for(let i=0;i<1000000000;i++){
}
console.timeEnd('START')
性能测试-测时间
(5).add(3).minus(2)
类和实例 在类的原型上构建方法并且能够实现链式写法的
-
实例调取方法,把方法放到当前所属类的原型上
-
链式调用:return this。返回结果为当前类的实例
Number.prototype.add=function(n){
// if(typeof n !== 'number' && !(n instanceof Number)) return;
if(!(typeof n === 'number' || n instanceof Number)) return this;
return this+n;
}
Number.prototype.minus=function(n){
if(typeof n !== 'number' && !(n instanceof Number)) return this;
return this-n;
}
console.log((5).add(3).minus(2));
(function(){
// 处理参数的容错性
function check(n){
n=Number(n);
return isNaN(n)?0:n;
// return n=Number(n) && isNaN(n)?0:n; // 其实这样n没改
// 测试
console.log(n=Number(n));
console.log(n);
// console.log(bool);
}
check('3');
function add(n){
n=check(n);
return this+n;
}
function minus(n){
n=check(n);
return this-n;
}
['add','minus'].forEach(item=>{
// 字符串eval,转换成一个js表达式=>代表的就是当前函数
Number.prototype[item]=eval(item);
})
})();
// 不能直接5.add,得用小括号()包起来
console.log((5).add(3).minus(2));
箭头函数和普通函数的区别
语法上更加简洁:
小括号,箭头,大括号;方法中函数体中只有一行代码把大括号省略return省略,不需要function,只有一个参数时小括号不用加
箭头函数中的this没有自己的this,是它上级作用域中的this;只跟上下文有关系,使用call apply无法改变
没有arguments,基于...args获取传递的参数。而且args本身就是个数组。(其实箭头函数比普通函数都要好那么一点点)
不能被new执行。没有prototype
注意:...args不是箭头函数独有的
function fn(a,...args){
console.log(a);
console.log(args);
}
fn(3,8,9);
/**
* 1.语法更简洁
*/
let fnB=function(x){
return function(y){
return x+y;
}
}
let fnA=x=> y=>x+y;
console.dir(fnB)
console.dir(fnA)
/**
* 2.所处上下文中的this
*/
let obj={name:'obj1'}
function fn1(){
console.log(this);
}
let fn2=()=>{
console.dir(this)
}
fn1.call(obj);
fn2.call(obj);
document.body.onclick=function(){
//this:body
let arr=[1,3,3]
// sort中 this:arr
arr.sort(function(a,b){
// 回调函数 this:window
return a-b;
})
arr.sort((a,b)=>a-b)
}
function each(arr,callBack){
for(let i=0;i<arr.length;i++){
// let flag=callBack(arr[i],i);
// 函数执行前面没点,所以this默认是window
// 这样改变callBack中的this
let flag=callBack.call(arr,arr[i],i);
// 这里还能支持返回值结束循环
if(!flag) break;
}
}
each([10,20,30,40],function(item,index){
return true;
})
扩展一下回调函数
把函数当作实参传给另一个方法,在另一个方法中会执行你这个回调
-
传递异步操作的结果,外部的回调函数就能接收到
-
函数执行一定会有返回值,内部能接收到回调的返回值
思考题
each
Array.prototype.each=function(callBack,obj){
obj=obj || window;
for(let i=0;i<this.length;i++){
if(callBack.call(obj,arr[i],i)===false){
break;
}
this[i]=callBack.call(obj,arr[i],i);
}
}
let arr=[10,20,30,'AA',40]
arr.each(function(item,index){
if(isNaN(item)){
return false;
}
return item*10;
},{name:'obj'})
console.log(arr);
*replace重写
用exec
let str=`<a href=http://www.baidu.com></a>
<p>ppppppppppppppppppppppppppppp</p>
<a href=http://www.bilibili.com></a>
<span>pppppppppppppppppppppppp</span>`
let reg=/<a href=(.*)>.*<\/a>/g
console.log(str.match(reg));
// console.log(str.matchAll(reg));
console.log(str.replace(reg,function(...args){
console.log(args)
return 'xxx'
}))
String.prototype.replaceMy=function(reg,callback){
let mat=null;
let matInfo=[]
let res=''
while(mat=reg.exec(this)){
/**
* exec的结果是一个数组
* 0:匹配到的所有内容
* 1:分组1
* 2:分组2
* ......
*/
console.log('mat===');
console.log(mat);
// 1.回调中就可以拿到匹配的完整内容和下面的分组
res=callback(...mat);
// 2.替换
// 存储信息
matInfo.push({
index:mat.index,
context:mat[0],
})
}
// 这里稍微有一点算法
let pos=0;
let strNew=''
for(let i=0;i<matInfo.length;i++){
let {index,context}=matInfo[i];
for(;pos<index;pos++){
strNew+=this[pos]
}
strNew+=res;
pos+=context.length;
}
let lastInfo=matInfo[matInfo.length-1];
const {index,context}=lastInfo;
let last=index+context.length
if(last<this.length){
for(;last<this.length;last++){
strNew+=this.charAt(last);
}
}
return strNew;
}
console.log(str.replaceMy(reg,function(...args){
return 'xxx'
}))
大小写取反
-
toUpperCase之后是否等于自己
-
通过码值判断
let str='wetest QQ net 云真机';
str=str.replace(/[a-zA-Z]/g,function(context){
if(context.toUpperCase()===context){
return context.toLowerCase()
}
return context.toUpperCase();
})
console.log(str);
用正则更简洁
字符串匹配
循环方法
// 业务逻辑实现
let s='abcdefghijklmn'
let t='aaa';
let len=t.length;
let flag=true;
for(let i=0;i<s.length-len+1;i++){
flag=true;
for(let j=0;j<len;j++){
if(s[i+j]!==t[j]){
flag=false;
break;
}
}
if(flag) break;
}
if(flag){
console.log('存在')
}else{
console.log('不存在')
}
(function(){
String.prototype.myIndexOf=function(t){
// this:s t
let lenS=this.length;
let lenT=t.length;
let index=-1;
// 如果原始字符串长度小,直接返回-1
if(lenS<lenT) return index;
// 这里要+1
for(let i=0;i<lenS-lenT+1;i++){
// 这里用了字符串方法slice
if(this.slice(i,i+lenT)===t){
index=i;
break;
}
// 每次循环前index赋值为i
// index=i;
// for(let j=0;j<len;j++){
// if(this[i+j]!==t[j]){
// index=-1;
// break;
// }
// }
// if(index>-1) break;
}
return index;
}
let i='wetest try perfdog'.myIndexOf('try');
console.log(i);
i='wetest try perfdog'.myIndexOf('my');
console.log(i);
})();
正则方法
-
把一个变量变成正则,需要new RegExp的方式
-
或者用eval
(function(){
String.prototype.myIndexOf=function(t){
// this:s t
// let reg=eval(`/${t}/`);
let reg=new RegExp(t);
let res = reg.exec(this);
return res?res.index:-1;
}
let i='wetest try perfdog'.myIndexOf('try');
console.log(i);
i='wetest try perfdog'.myIndexOf('my');
console.log(i);
console.log(/a/.test('abcdef'));
})();
对象的属性值
-
字符串100和数字100是一个属性
-
对象属性会自动调toString方法转换成字符串[object Object],所以也永远是一个属性
(function(){
var a={},b='123',c=123;
a[b]='b'
a[c]='c'
console.log(a);
console.log(a[b]);
})();
(function(){
var a={},b=Symbol('123'),c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a);
console.log(a[b]);
})();
(function(){
var a={},b={key:123},c={key:456};
a[b]='b';
a[c]='c';
// 遇到对象属性名会默认转换成字符串
// 这个是toString是做类型检测的
console.log('toString===')
console.log(Object.prototype.toString.call(b));
console.log(Array.prototype.toString.call([11,22]));
console.log(a);
console.log(a[b]);
})();
用正则检测URL
域名的写法:什么什么点,什么点。可能3组可能4组
出现的是,数字字母下划线,加上中杠
www.baidu.com
wetest.qq.comkbs.sports.qq.com
// 项目中的做法
export function isUrl(url: string) {
return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?/.test(url);
}
let url='https://www.baidu.com/index.html?query=abc#href';
let reg=/http(s)?:\/\/([\w-]+\.)+[a-z0-9]+(\/[\w./%-?#&=])?/;
let reg1=/(http(?:s)?:\/\/)((?:[\w-]+\.)+[a-z0-9]+)((?:\/[^/?#]+)+)?(\?[\w-&=%]+)?(#[\w-%]+)?/
console.log(reg.test(url));
console.log(reg1.exec(url));
正则相关
我要知道这是第几个分组,只要从左到右数第几个括号就行
?: 非捕获分组
构造函数
function Foo(){
Foo.a=function(){
console.log(1)
}
this.a=function(){
console.log(2);
}
}
Foo.prototype.a=function(){
console.log(3);
}
Foo.a=function(){
console.log(4);
}
Foo.a();
let obj=new Foo();
obj.a();
Foo.a();
编写代码实现图片的懒加载
背景原因
为什么要做图片懒加载:
加载css,加载html,加载js,从服务器拿到数据完成数据绑定,
其实每一张图片都是属于比较大的资源;几MB,十几KB,二十几KB;其实每一张图片就跟一个css一个js差不多……其实用户只看到一屏,图片延后加载是非常重要的,优化页面性能
加载分析
-
页面加载完,第一屏的时候
$(document).ready: 这个指的是dom结构加载完
$(window).onload: 整个页面资源加载完。当页面所有资源都加载完成时候
这2个是不一样的要注意区分
-
鼠标滚轮滚动的时候
图片加载的条件,当图片完全出现在视野中
这里有2个概念,浏览器和页面body;
滚动的时候页面会往上走,图片距离页面body顶端的距离是不变的,这个不会随着你怎么动而改变的;图片的位置一定A的值就定下来了;dom:offset
B:浏览器底边距离页面顶端的偏移
$A: imgBox.offsetHeight+offsetTop,自身的高度+距离父级的上偏移
$B: document.documentElement.clientHeight 一屏的高度 + 滚动条卷去的高度
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" crossorigin="anonymous"></script><script></script>
<style>
.container{
margin-top: 1000px;
}
/*
通常让.imgBox与img宽高比例保持一致,避免图片变形
*/
.imgBox{
background-color: #adb5bd;
width: 867px;
height: 181px;
margin-bottom: 30px;
}
img{
display: none;
width: 100%;
}
</style>
</head>
<body>
<div class="container">
<!-- <div class="imgBox">-->
<!-- <img src="" alt="" data-src="https://zhufengpeixun.oss-cn-beijing.aliyuncs.com/banner022.png">-->
<!-- </div>-->
<!-- <div class="imgBox">-->
<!-- <img src="" alt="" data-src="https://zhufengpeixun.oss-cn-beijing.aliyuncs.com/banner022.png">-->
<!-- </div>-->
<!-- <div class="imgBox">-->
<!-- <img src="" alt="" data-src="https://zhufengpeixun.oss-cn-beijing.aliyuncs.com/banner022.png">-->
<!-- </div>-->
</div>
<script>
let str=new Array(20).fill('<div class="imgBox">\n' +
' <img src="" alt="" data-src="https://zhufengpeixun.oss-cn-beijing.aliyuncs.com/banner022.png">\n' +
' </div>').join('')
document.querySelector('.container').innerHTML=str;
let $imgBoxs=$('.container .imgBox');
let $window=$(window);
$window.on('scroll',function () {
let $B=$window.outerHeight()+$window.scrollTop();
Array.prototype.forEach.call($imgBoxs,box=>{
// box是dom对象需要$(...)一下
let $img=$(box).children('img');
if($img.attr('isloading')) return;
console.log('ok');
// offsetHeight + offsetTop
let $A=$(box).outerHeight()+$(box).offset().top;
// document.documentElement.clientHeight + scrollY
if($A<=$B){
$img.attr('src',$img.attr('data-src'));
$img.on('load',function () {
// $img.css('display','block');
// $img.show(2000);
$img.fadeIn("slow");
})
$img.attr('isloading',1)
}
})
})
</script>
</body>
</html>
正则包含大小写和数字
正向预查:其实就是条件
// 突然发现正则很灵活,单个字符是A或者a
let reg=/(?!^[A-Za-z]+$)(?!^[A-Z0-9]$)(?!^[a-z0-9]$)^([a-zA-Z0-9]{6,16})$/
console.log(reg.exec('AAAaaa111'))
console.log(reg.exec('AAAaaa'))
console.log(reg.exec('AAAAAA'))
console.log(reg.exec('aaaaaa'))
console.log(reg.exec('111111'))
console.log(reg.exec('AAA111'))
console.log('-------------------------')
reg=/(?!^[0-9a-zA-Z]+$)^[0-9a-zA-Z-]{1,10}$/
console.log(reg.exec('aaaaaaa'))
console.log(reg.exec('aaaaaaa---'))
加上了^...$:就表示只能是这个
这个点是有点晕:后面加了^$的,前面(?=)里也要加
非必须,?=表示的就是包含;
必须,?!排除掉一些情况;
遍历属性选择器
-
首先获取页面上所有的标签
-
然后再循环每个元素去判断attribute
function $attr(property,value){
// 所有元素节点
let elements=document.getElementsByTagName('*');
let arr=[];
Array.prototype.forEach.call(elements,elem=>{
if(property==='class'){
let reg=new RegExp(`\\b${value}\\b`,'g')
if(reg.test(elem.getAttribute(property))){
arr.push(elem);
}
}else if(elem.getAttribute(property)===value){
arr.push(elem)
}
})
return arr;
}
$attr('class','box');
正则给英文单词加空格
let reg=/\b[a-zA-Z]+\b/g;
let str='big大花园dog'
str=str.replace(reg,function(val){
return ` ${val} `;
})
console.log(str);