牛客网web面试题总结汇总

一、为什么要有cookie

http是无状态协议,服务器不记录客户端的状态,无法识别客户端。举个例子,假如你在牛客网上刷题,需要登录,你登录后,打开刷题的链接,但服务器无法识别你之前是否登录,你就要在这个刷题的页面再次登录,若要打开同一个网站的多个网页,就需要重复登录,这个过程很麻烦也很费时,所以就引入了cookie技术。cookie是以小的文本形式存在于客户端,cookie保存了登录的凭证,只要下次请求时带着cookie发送,不必重新输入用户名和密码重新登了。

什么是cookie

cookie技术是在请求和响应报文中写入cookie信息来控制客户端的状态。当你首次浏览某网站时,这个网站会在发送报文中设置set-cookie的首部字段信息,在你的电脑上保存了cookie,这是一个小的文本文件,记录了你的用户名、密码、浏览的网络信息等,当你再次来到这个网站时,服务器会先看看有没有他上次留下来的

说一说cookie、sessionStorage、localStorage的区别

1.数据存储位置
都是存储在浏览器本地的,cookie是由服务器端写入的,sessionstorage、localstorage都是由前端写入的,
2.生命周期
cookie的生命周期是由服务器端在写入时候就设置好的,localStorage是写入就一直存在,除非手动清除。sessionstorage是页面关闭时候自动清除。
3.存储空间比较
cookie的存储空间大概4KB,SessionStorage、LocalStorage存储空间较大,大概有5M。
4.数据共享
cookie、sessionstorage、localstorage数据共享遵循同源原则,sessionStorage限制是同一个页面。
5.发送请求
前端发送给后端的请求中会自动携带cookie数据,但sessionStorage、LocalStorage不会。
6.应用场景
cookie一般用于存储登录验证信息SessionID或者token,localStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否刷新进入页面。

cookie的设置以及发送过程分为以下四步:

牛客网web面试题总结汇总_第1张图片
1.客户端发送一个http请求到服务器端
2.服务器端发送一个http响应到客户端,其中包含Set-Cookie头部牛客网web面试题总结汇总_第2张图片

3.客户端发送一个http请求到服务器端,其中包含Cookie头部
4.服务器端发送一个http响应到客户端
牛客网web面试题总结汇总_第3张图片

2022/6/22

JS的数据类型有哪些,区别是什么?

基本数据类型为:number、string、Boolean,Null,undefined,BigInt,Symbol七种。
引用数据类型:Object、Array、regexp.
2.存储方式不同
(1)基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
(2)引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
3.Symbol是ES6新出的一种数据类型
这种数据类型的特点是没有重复的数据,可以作为object的key。
4.BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。

说说你对闭包的理解

1.理解闭包就要去理解变量的作用域,在js上有2种作用域,一种是全局变量,一种是局部变量。在函数内部可以读取全局变量,但在函数外部无法读取函数内部的局部变量。
闭包是指有权访问另一个函数作用域中的局部变量的函数。声明在一个函数中的函数,叫做闭包函数。内部函数可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回之后。

闭包的特点

1.让外部访问函数内部成为了可能。
2.局部变量会常驻在内存中。
3.可以避免使用全局变量,防止全局变量污染。
4.会造成内存泄漏。
例1

var age = 10;
function foo(){
    console.log(age);//-----------------1
	var name = "hunt_bo";
	return function(){
		console.log(name);
	}
}
console.log(name);//---------------2
var bar = foo();
bar();

在这个闭包的事例中,首先函数内部是可以访问全局变量的,在foo()函数中可以输出age的值为10,但从全局环境中是不能访问到函数中的name的。在用到闭包时,在foo函数内返回一个函数,在返回的函数中用到局部变量name,在外部调用时,例如bar(),可以使用到函数foo()中的属性name.
例2:

function addCount(){
	var count = 0;
	return function(){
		count += 1;
		console.log(count);
	}
}
var fun1 = addCount();
var fun2 = addCount();
fun1();//1
fun1();//2
fun1();//3
fun2();//1
fun2();//2

每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。所以说虽然fun1和fun2都是addCount(),但是呢都创建了新地址,都是自己的,互不干扰。

2022/7/4

说一说promise是什么与使用方法?

promise是异步编程的解决方案,其实是一个构造函数,自身有all、reject、resolve这几个方法,可以进行异步操作。
promise有以下特点:
(1)promise对象不受外界的影响。promise对象有3种状态分别为:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作无法改变这个状态。
(2)promise对象改变的原因有2种,从pending变为fulfilled和从pending变为rejected。
promise对象可以避免层层嵌套的回调函数,而且提供了统一的接口,使得异步操作更加容易。

基本用法

Promise对象是一个构造函数,用来生成promise实例。
创造一个promise实例

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

(1)Promise函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是2个函数,有javascript引擎提供。
(2) 一旦状态改变,就不会再变,任何时候都可以得到这个结果。其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束),resolve函数的作用为将promise对象的状态从"pending"变为"fulfilled",在异步操作成功完成后,将异步操作的结果作为参数传递出去。reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
(3)可以使用promise的then方法来指定fulfilled状态和rejected状态的回调函数

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。

下面是一个Promise对象的简单例子。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

2022/12/31

说一说跨域是什么?如何解决跨域问题?

同源是一种约定,是浏览器最核心的安全功能。同源策略会阻止一个域的js脚本与另一个域的内容进行交互。同源协议是指2个页面的URL具有相同的协议、域名、端口号。

什么是跨域

当请求的URL的协议、域名、端口号三者之间任意一个与当前页面的URL不同即为跨域.

跨域产生原因

跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。

三、当跨域时的限制

(1)无法读取非同源网页的Cookie、LocalStorage、IndexedDB
(2)无法读取非同源网页的dom
(3)无法向非同源地址发送AJAX请求

四、跨域解决方法

1.CORS

使用 跨域资源共享CORS(Cross-Origin Response Sharing),基本思路是用自定义的HTTP头部允许浏览器和服务器相互了解,以确定请求或响应是成功还是失败。

对于GET或POST请求,发送请求报文时会有一个额外的头部叫origin,origin头部包含发送请求的页面的源(协议、域名、端口号),以便服务器确定是否为其提供服务。下面是一个origin头部的实例
Orgin:http://www.nczonline.net

如果服务器决定响应请求,那应该发送Access-Control-Allow-Origin头部,包含相同的源;或者如果资源是公开的,则就包含*,比如
Access-control-Allow-Origin:http://www.nczonline.net
或者:Access-Control-Allow-Origin:*
若没有以上头部,或有了但不匹配,则表明服务器不会响应浏览器的请求。否则,服务器就会响应这个请求。
res.setHeader(‘Access-Control-Allow-Origin’, ‘*’);

说一说BFC

1.什么是BFC

BFC (Block Formatting Context) 块格式化上下文,是web页面的一块独立渲染区域,内部元素的渲染不会影响边界以外的元素区域,外部的元素也不会影响BFC内部的元素。BFC属于文档中的普通流。

2.如何创建BFC

1.float的值不是none,float的值为:left,right或inherit。
2.position的值不是static或relative,是absolute或fixed。
3.display的值是inline-block,table-cell,flex,table-caption,inline-flex.
4.overflow的值不是visible,是hidden,auto或scroll。

3.bfc的作用

1.利用bfc避免margin重叠

当2个div的盒子竖直排列时,2个盒子中间的间距为1个margin,也就是60px,而不是120px,这就是margin的重合

<head>
<style>
    .cube{
        width:100px;
        height:100px;
        border:1px solid red;
        margin:60px;
      
    }
    style>
    head>
 
    <body>
<div class="cube">div>
<div class="cube">div>
body> 

我们可以看到2个div的盒子间的距离为1个margin为60px.为margin合并,
牛客网web面试题总结汇总_第4张图片

我们采用BFC来解决margin重合,分别设置2个container,让父容器达到BFC,将2个container的overflow设为hidden,

<head>
<style>
    .cube{
        width:100px;
        height:100px;
        border:1px solid red;
        margin:60px;
     
    }
  .container{
       overflow:hidden;
    }
    style>
head>
<body>   
   
    <div class="container">
    <div class="cube">div>
    div>


   <div class="container">
    <div class="cube">div>
  div>

body>

牛客网web面试题总结汇总_第5张图片

2.清除浮动的影响

父容器container中有一个子元素,让子元素达到左浮动,我们理想得到的是一个包含子容器的盒子,即一个红色的边框包含着蓝色的正方形。但实际得到的却是下图,这是因为子元素实现了左浮动,脱离了标准文档流。

<head>
   
<style>
    .cube{
        width:100px;
        height:100px;
       background:blue;
        margin:60px;
     float:left;
    }
  .container{
     
       border:1px solid red;
    
    }
    style>
head>
<body>   
   
    
    <div class="container">
    <div class="cube">div>
   div>
body>

牛客网web面试题总结汇总_第6张图片
我们为父元素添加overflow:hidden,为父元素形成BFC,

<head>
   
<style>
    .cube{
        width:100px;
        height:100px;
       background:blue;
        margin:60px;
     float:left;
    }
  .container{
     
       border:1px solid red;
    overflow: hidden;
    }
    style>
head>
<body>   
    <div class="container">
    <div class="cube">div>
   div>
body>

牛客网web面试题总结汇总_第7张图片

3.阻止元素被浮动元素所覆盖

当2个块元素因为一个元素的左浮动而导致2个元素相互重叠时,如下图:

<head>
   
<style>
    .cube1{
        width:100px;
        height:100px;
       background:blue;
       
     float:left;
    }
    .cube2{
        width:200px;
        height:200px;
       background:pink;
     
    
    }
 
    style>
head>
<body>   
    <div class="cube1"> div>
    <div class="cube2">div>
  
body>

牛客网web面试题总结汇总_第8张图片
我们可以将其中一个元素设为BFC来消除这个影响

<head>
   
<style>
    .cube1{
        width:100px;
        height:100px;
       background:blue;
       
     float:left;
    }
    .cube2{
        width:200px;
        height:200px;
       background:pink;
     overflow: hidden;
    
    }
 
    style>
head>
<body>   
    <div class="cube1"> div>
    <div class="cube2">div>
  
body>

牛客网web面试题总结汇总_第9张图片

20230103

1.说一说JavaScript有几种方法判断变量的类型?

1.有4种方法判断变量的类型,分别是typeof、instanceof,Object.prototype.toString.call(),constructor。

1.typeof

typeof:用于判断基本数据类型typeof能判断7种,分别是undefined,boolean,string,number,object,function,symbol

 let message="some ";
            console.log(typeof(message));         //string
            console.log(typeof(1));               //number
            console.log(typeof("1")); //string
            console.log(typeof(true)); //boolean
            console.log(typeof({})); //object
            console.log(typeof([])); //object
            console.log(typeof(null)); //object
            console.log(typeof(()=>{})); //function
            console.log(typeof(undefined)); //undefined
            console.log(typeof(Symbol(1))) //symbol

若要理解instanceof,先理解原型

理解原型:

只要创建一个函数,就会为这个函数创建一个prototype属性,这个属性是个对象。所有的原型对象自动获得一个名为constructor的属性,指向其构造函数。对于下面的例子,Person.prototype.constructor指向Person。

constructor用于指向实例的构造函数

每个对象上有__proto__属性,用于对象实例来访问原型对象。

prototype是函数具有的属性,用于指向函数的原型对象。__proto__是对象具有的属性,用于指向对象的原型对象。

 function Person(){}
       console.log(Person.prototype);//Object   说明Person.prototype是一个Object对象
       console.log(typeof Person.prototype);//object     说明原型对象的类型是object对象
       console.log(Person.prototype.constructor===Person);//true   原型对象有一个属性是constructor,constructor指向原型对象的构造函数是Person
        console.log(Person.prototype===Object); //false  这里之所以是false,因为这里的Object表示的是构造函数,但Person.prototype表示的是对象,所以为false

        console.log(Person.prototype.__proto__===Object.prototype);//true  原型链都会终止于Object函数的原型对象
        console.log(Person.prototype.__proto__.constructor===Object);  //true  因为Person.prototype.__proto__相当于Object.prototype。Object函数的原型对象的constructor为Object函数
        console.log(Person.prototype.__proto__.__proto__===null); //Object的原型的原型为null
        console.log(Person.prototype.__proto__);  //为Object对象

实例通过__proto__链接到构造函数的原型对象,
构造函数通过prototype属性链接到原型对象
实例通过__proto__链接到构造函数的原型对象,再通过constructor链接到构造函数

  function Person(){}
       let person1=new Person();
       let person2=new Person();
       console.log(person1.__proto__===Person.prototype);  //true
       console.log(person1.__proto__.constructor===Person); //true

由以上信息可以得到下图:

Person.prototype.constructor=Person;//1
Object.prototype.constructor=Object;//2
Person.prototype.__proto__=Object.prototype;//3//这条很重要
Object.prototype.__proto__=null;//4

用Person.prototype.__proto__来表示Object.prototype代入到2式和4式中,可以推导得到:

Person.prototype.__proto__.constructor=Object
Person.prototype.__proto__.__proto__=null

牛客网web面试题总结汇总_第10张图片
增加实例对象后的图像:

  function Person(){}
       let person1=new Person();
       let person2=new Person();
       console.log(person1.__proto__===Person.prototype);  //true
       console.log(person1.__proto__.constructor===Person); //true

牛客网web面试题总结汇总_第11张图片

牛客网web面试题总结汇总_第12张图片

同一个构造函数构造的2个实例共享1个原型对象

 function Person(){}
       let person1=new Person();
       let person2=new Person();
       console.log(person1.__proto__===person2.__proto__);  //true

2.instanceof

instanceof:用于区分引用数据类型,检测方法是构造函数的原型对象是否在在当前实例的原型链上,用instanceof检测出来的结果都是true,不太适合用于简单数据类型的检测,对于简单数据类型的undefined,null,symbol检测不出来。

例1:instanceof分别检验构造函数Person的prototype属性是否在person1这个实例的原型链上。
例2: instanceof分别检验构造函数Object的prototype属性是否在person1这个实例的原型链上。

  function Person(){}
       let person1=new Person();
      //在person1的原型链上找构造函数,原型链终止于Object.prototype
       console.log(person1 instanceof Person);  //true 因为person1.__proto__为Person.prototype
        console.log(person1 instanceof Object); //true 因为person1.__proto__为Person.prototype,Person.prototype.__proto__为Object.prototype,也就是person1.__proto__.__proto__为Object.prototype
        console.log(Person.prototype instanceof Object); //true 因为Person.prototype.__proto__为Object.prototype 

person1,person2对象既是Object实例,也是Person的实例

  function Person(name,age,job){
            this.name=name;
            this.age=age;
            this.job=job;
           }

           let person1=new Person("Nichelous",29,"software");
           let person2=new Person("Greg",13,"Doctor");
           
           console.log(person1 instanceof Object);//true
           console.log(person1 instanceof Person);//true
           console.log(person2 instanceof Object);//true
           console.log(person2 instanceof Person);//true
             console.log( ({}) instanceof Object);//true
          console.log( [] instanceof Array);//true

明白了instanceof的功能和原理后,可以自己实现一个instanceof同样功能的函数:

原型链其实就是一个类似链表的数据结构。

instanceof做的事,就是在链表上寻找有没有目标节点。从表头节点开始,不断向后遍历,若找到目标节点,返回true。遍历结束还没找到,返回false。

  function Person(){}
      function Person5(){}

      let person1=new Person();


     function myinstanceof(obj,constructor){
       let objPrototype=obj.__proto__; //创建实例的原型对象
       let conPrototype=constructor.prototype;//创建构造函数的原型对象
       while(objPrototype!=null) //原型链会终止于null
       {
         if(objPrototype==conPrototype)  //若找到,返回true
         return true;
         objPrototype=objPrototype.__proto__;  //继续遍历原型链
       }
       return false;
     }

     console.log(myinstanceof(person1,Person)); //true
     console.log(myinstanceof(person1,Object)); //true
     console.log(myinstanceof(person1,Person5)); //false

constructor

用于检测引用数据类型,检测方法为获取实例的构造函数与某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类加入进来,避免干扰。

console.log((1).constructor === Number);    // true
console.log(''.constructor === String);     // true
console.log(false.constructor === Boolean);  // true
console.log([].constructor === Array);     // true
console.log(({}).constructor === Object);   // true
console.log((function foo() {}).constructor === Function);// true
console.log(Symbol('1').constructor === Symbol);// true

//能判断如下类型 : Number , String , Boolean , Symbol , Function , Object , Array ; null 和 undefined没有constructor属性,所以不能判断

Object.prototype.toString.call()

Object.prototype.stoString.call()返回的是该数据类型的字符串,各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法.

let obj = {}
  
        (1) Object.defineProperty(obj,Symbol.toStringTag,{
            get:function(){
                return 'hello'
            }
         })
 
       (2) Object.prototype[Symbol.toStringTag] = 'hello'
 
      console.log(Object.prototype.toString.call(obj))    //[object,hello]

function Foo(){};
const foo = new Foo();
Object.prototype.toString.call(1); // '[object Number]'
Object.prototype.toString.call('1'); // '[object String]'
Object.prototype.toString.call(true); // '[object Boolean]'
Object.prototype.toString.call(undefined); // '[object Undefined]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(Symbol(1)); // '[object Symbol]'
Object.prototype.toString.call(Foo); // '[object Function]'
Object.prototype.toString.call(foo); // '[object Object]' , 实例化对象属于Object
Object.prototype.toString.call({}); // '[object Object]'
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(NaN); // '[object Number]' ,此方法判断NaN也属于Number

20230107

说一说样式优先级的规则是什么?

!important>行内样式>嵌入样式>外链样式>id选择器>类选择器>标签选择器>复合选择器>通配符>继承样式

CSS样式的优先级应该分成四大类

第一类:
!important的优先级最高,
如果a标签不加!important特性,那么显示的颜色为蓝色,加了!important之后优先级提高了,显示颜色为绿色。

   <style>
       #main a{
         color:blue;
       }
      a{
        color:green !important;
      }
     style>
     
     head>
     <body> 
       <div id="main">
         <a>实例a>     
       div>  

在下面的代码中,box的所有div中字体为红色,我们用class定义了ikun1,ikun2自身的颜色为蓝色,ikun1未使用!important,ikun2使用了!important。

因为class的优先级小于id,所以ikun1即使使用了类来定义自身属性,也无法生效,颜色为红色。但ikun2使用了!important来提升优先级,所以ikun2的颜色为蓝色。

<head>
   
     <style>
     #box div{
        color:red;
     }
     .ikun1{
      color:blue;
     }
     .ikun2{
      color:blue !important;
     }
         style>
     head>
     <body> 
       <div id="box">
       <div class="ikun1">ikun1div>
       <div class="ikun2">ikun2div>  
        div>
     body>

第二类引入方式
行内样式>内嵌样式>外链样式

由以下代码可知,ikun1的颜色为红色,ikun2,和ikun3的颜色为蓝色,即行内样式>内嵌样式。

<head>
     <style>
  div{
    color:blue
  }
         style>
     head>
     <body> 
       <div id="box">
       <div style="color:red">ikun1div> 
       <div>ikun2div>  
       <div>ikun3div>
        div>
     body>
     

第三类:
选择器的优先级:
id选择器>(类选择器|伪类选择器|属性选择器)>(后代选择器|伪元素选择器)>(子选择器|相邻选择器)>通配符选择器。

第四类:
样式的继承:我们为一个元素设置的样式同时也会被应用到它的后代元素上,继承是发生在祖先后代之间的。

继承样式的优先级是所有样式中优先级较低的。

例如如下代码,我们给box设置字体颜色为蓝色,box的子元素ikun1,ikun2就继承了box的字体颜色属性。

<head>
     <style>
  div{
    color:blue
  }
         style>
     head>
     <body> 
       <div id="box">
       <div style="color:red">ikun1div> 
       <div>ikun2div>  
       <div>ikun3div>
        div>
     body>
     

说一说JS实现异步的方法?

实现异步的方法有:
回调函数、事件监听、setTimeout、Promise、生成器Generators/yield、async/awt

同步和异步

同步
指在主线程排队执行的任务,只有前一个任务执行完毕后,后一个任务才能继续执行。

调用一旦开始,这个调用必须返回结果,才能继续向下执行。程序执行的顺序与任务排列顺序是一致的。

异步

异步任务不进入主线程,而进入任务队列,只有任务队列通知主线程,某个异步任务可以进行了,该任务才会进入主线程。

每一个任务有一个或多个回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务不等前一个任务结束就执行。

程序的执行顺序与任务的排列顺序是不一致的,异步的。

我们常用的setTimeout和setInterval函数,Ajax都是异步操作。

1.回调函数

把函数A当做参数传到另一函数B中,当需要这个函数A时,再调用这个A函数,这个A函数称为回调函数。

function add(num1,num2,callback){
var sum=num1+num2;
callback(sum);
  }

  function print(num){
    console.log(num);
  }
  add(5,6,print);

分析:在add(5,6,print)中,函数print作为参数传入到add函数中,但并不是马上起作用,而是在var sum=num1+num2;运行完成后需要打印输出sum时才会调用这个函数。

回调函数特点:

1.不会立即执行,回调函数作为参数传递给一个函数时,传递的是参数的定义并不会立即执行。回调函数在调用函数中要通过()运算符才会执行。

2.回调函数是一个闭包,也就是说它能访问到其外层定义的变量。

回调函数this的指向问题

正常this指向问题:
什么是this:自动引用正在调用当前方法的对象。
this指向的三种情况:

  1. obj.fun() fun中的this指向obj,自动指向当前的对象。
  2. new Fun() Fun中的this指向正在创建的新对象,new改变了函数内部的this指向,导致this指向了实例化new的对象。
  3. fun()和匿名函数自调 this默认指向window,函数内部的this,this默认指向window。

再来看回调函数的this指向问题


function create(callback){
callback();
}

 var obj={
   data:100,
  total:function(){
    create(function(){
      console.log(this); //window
    });
  }
 }
 obj.total();

结果:window
回调函数中的this默认指向window,因为本质上回调函数callback并没有当前对象的调用。

解决方法:使用箭头函数,箭头函数根本没有自己的this,导致内部的this就是外层代码的this。因为它没有this,所以不能用作构造函数。

function create(callback){
callback();
}

 var obj={
   data:100,
  total:function(){
    create(()=>{
      console.log(this); //window
    });
  }
 }
 obj.total();

结果:{data: 100, total: ƒ}
输出的就是obj对象。

20230108

说一说Vue2.0 双向绑定的原理与缺陷?

Vue使用Object.defineProperty()来实现数据的双向绑定。

首先来看这个函数Object.defineProperty(),这个方法接收3个参数:

1.属性所在的对象
2.属性的名字
3.一个描述符对象

描述符对象的属性可以包含:configurable,enumerable,writable,value。

1.configurable:表示能否通过delete删除属性,能否修改属性的特性,默认值为false

2.enumerable:表示能否通过for-in循环访问属性,默认值为false

3.writable:表示能否修改属性值,默认为false

4.value:这个属性的数据值,默认为undefined。

let person={};
Object.defineProperty(person,"name",{
  writable:false,
  value:"nicholas"
});
console.log(person.name); //nicholas
person.name="bob";
console.log(person.name); //nicholas

我们可以看到2个输出的都是Nicholas,name属性的值是无法更改的。

let p1={};
Object.defineProperty(p1,"name",{
    configurable : false,
    value:"lisi"
})
console.log(p1); //{ name: 'lisi' }
delete p1.name;
console.log(p1); //{ name: 'lisi' }

我们可以看到属性是无法删除掉的。

Object.defineProperty()的第三个属性除了可以是数据属性,也可以是访问器属性。

访问器属性包含获取(getter)函数和设置(setter)函数,

get:获取函数,在读取属性时调用。默认值undefined。
set:设置函数,在设置属性时调用。默认值undefined。

let cai={
  name:"kunkun",
  ikun:120,
  love:"basketball"
}

Object.defineProperty(cai,"fans",{
  get(){
    return this.ikun;	//返回的是ikun的属性的值
  },
  set(newValue){         //当fans属性更新时,传入的参数是newValue
  this.ikun=newValue;     //让ikun属性随着fans属性的更新而更新
  }
});
console.log(cai.ikun);  //120
console.log(cai.fans);  //120 返回的就是this.ikun

cai.fans=200;
console.log(cai.ikun); //200 当fans属性更新时,ikun属性也随之更新

cai.ikun=300;
console.log(cai.fans);//300 

代码实现了fans属性与ikun属性的双向绑定,fans属性返回的值就是ikun的数据值,当ikun属性的值发生改变时,fans属性的值也发生了改变。当fans属性的值发生改变时,ikun属性的值也发生改变。

然后我们来看Vue实现数据的双向绑定原理:

Vue响应式指的是:组件的data发生变化,立刻触发试图的更新

原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。

通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom,使用Object.defineProperty API, 获取属性值会触发getter方法,设置属性值会触发setter方法,在setter方法中调用修改dom的方法 。

Object.defineProperty的缺点:

  1. 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
  2. 不能监听对象的新增属性和删除属性
  3. 无法正确的监听数组的方法,当监听的下标对应的数据发生改变时

实现数据的双向绑定代码:

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Documenttitle>

    <style>
      #myInput {
        width: 400px;
        height: 50px;
        font-size: 40px;
        color: red;
      }
      #contain {
        margin-top: 20px;
        width: 400px;
        height: 200px;
        border: 1px solid salmon;
      }
    style>
  head>
  <body>
    <input id="myInput" type="text" />
    <div id="contain">div>

    <script>
      var text;
      window.data = {};
      var oIn = document.getElementById("myInput");
      var oDiv = document.getElementById("contain");

      oIn.addEventListener("input", function (e) {
        text = e.target.value;
        console.log(text);
        window.data.value = text;
      });
      Object.defineProperty(window.data, "value", {
        get() {
          return "";
        },
        set(v) {
          oDiv.innerHTML = v;
        },
      });
    script>
  body>
html>

说一说null 和 undefined 的区别,如何让一个属性变为null

undefined类型只有一个值就是特殊值undefined,

使用var或let声明变量但没有初始化时

或者声明了变量并赋予初值为undefined

或者某个对象不存在某个属性却去访问

或者函数定义了形参却没有穿实参。

使用typeof判断undefined的类型为undefined。

console.log(typeof(undefined)); //undefined
let message;
console.log(message===undefined);//true
let ikun=undefined;
console.log(ikun===undefined);//true

console.log(undefined==undefined);//true
console.log(undefined===undefined);//true

null表示一个对象没有设置初始值,相当于一个对象没有设置指针地址,相当于空对象指针。

使用typeof来判断null的类型为object

console.log(typeof null); object
console.log(null==undefined);//true
console.log(null===undefined);//false

let car=null;
console.log(car);

undefined是由null派生而来的,用==来比较undefined和null时会返回true。

null 其实属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,GC 选择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。

说一下浮动?

1.浮动的作用:

1.可以实现文字环绕盒模型:

<head>
     <style>
   .one {
            width: 300px;
            height: 300px;
        }
        
        .one div {
            float: left;
            height:100px;
            width:100px;
            border:1px solid red;
        }
         style>
     head>
     <body>
      <div class="one"><div>div>
        <p>黑白棋该怎么玩呢?不妨一起来看看吧!希望能给大家带来帮助! 一般黑白棋棋盘共有8行8列共64格。 开局时,棋盘正中央的四格先放置黑白相隔的四枚棋子,双方轮流落子。 只要落子和棋盘上任一枚己方棋子在一条线上夹着对方棋子,就可将对方的这些棋子转变为我己方。 如果在任意位置落子都不能夹住对方的任一颗棋子,就要让对手下子。
        p>
    div>
  <script>
 script>
     body> 

牛客网web面试题总结汇总_第13张图片
1.设置了浮动的块级元素可以排列在同一行。

2.设置了浮动的行内元素可以设置宽高
例:

<head>
     <style>
   label{
  /*float:left;*/
     width:100px;
     height:200px;
     border:1px solid red;
   }
         style>
     head>
     <body>
      <label>行内元素的宽高随标签里的内容而变化。label>
  <script>
 script>
     body>

当行内元素没有设置浮动时,没有宽高,如下图:
在这里插入图片描述
当在label标签中设置浮动时,行内元素有了宽高,如下图:
牛客网web面试题总结汇总_第14张图片
3.可以按照元素浮动设置方向对齐排列盒子模型,例如左浮动或右浮动。

设置浮动元素的特点:

设置了浮动,该元素脱离了标准文档流,如果父级盒子没有设置高度,子元素设置为浮动,那么父级盒子会被子盒子撑开,这时父级盒子的高度就塌陷了。如果浮动元素后面还有兄弟盒子,那么兄弟盒子的布局也会被影响。

例以下为2个兄弟元素当son1没有设置左浮动时:

<head>
     <style>
   .son1{
  

     width:100px;
     height:200px;
     border:1px solid red;
   }
   .son2{
    width:100px;
     height:200px;
     border:5px solid green;
   }

         style>
     head>
     <body>
   
        <div class="son1">div>
        <div class="son2">div>

如图:
牛客网web面试题总结汇总_第15张图片
当给son1加了左浮动时,对son2元素的影响
牛客网web面试题总结汇总_第16张图片
2个兄弟元素重合到了一起。

清除浮动的方法

1. 伪元素清除

给父级元素添加伪元素
通过给父级元素条件伪元素,来实现解决父级盒子塌陷的问题:

<head>
     <style>
   .son1{
  
    float:left;
     width:100px;
     height:200px;
     border:1px solid red;
   }
  
   .father::after{
     display: block;  /* 使其成为块级元素后*/
     content: "";/*为伪元素加入空内容,以便伪元素中不会有内容显示在页面中*/
     height: 0;
     clear: both; /* 清除浮动*/
   }
   
   .father{
     *zoom:1;
     border:1px solid green;
   }
         style>
     head>
     <body>
   <div class="father">
        <div class="son1">div>
        <div class="son2">div>
 div>
  <script>
 script>
     body>
     

牛客网web面试题总结汇总_第17张图片

使用伪元素清除浮动,不会新增标签,不会有其他影响,是当下清除浮动最流行的方法。
2. 使用bfc,给父级元素添加overflow:hidden,不会新增标签,但是父级元素有定位元素超出父级,超出部分会隐藏,在不涉及父级元素有超出内容的情况,overflow:hidden常用。

20230109

说一说es6中箭头函数?

1.箭头函数是什么

箭头函数的实例与函数表达式的创建是相同的。

 let arrowSum=(a,b)=>{
   return a+b;
 };

 let expression=function(a,b){
   return a+b;
 }
 console.log(arrowSum(5,10));//15
 console.log(expression(5,10));//15

当有一个参数时,不用括号,只有没有参数或有多个参数时,使用括号。

let triple=x=>{return 3*x;};

let getRandom=()=>{return 2;};

let sum=(a,b)=>{return a+b}

箭头函数的特点:

  1. 箭头函数最大的特点是this是从外部获得的,是继承外部的执行上下文中的this,所以没有当前this的箭头函数也不能做构造函数。同时使用call()或apply()方法调用一个函数时,只能传递参数,不能绑定this。
 let obj={
   ikun1:()=>{
     console.log(this);
   },
   ikun2:function(){
     console.log(this);
   }
 }

obj.ikun1();//Window {window: Window, self: Window, document: document, name: '', location: Location, …}
obj.ikun2();//{ikun1: ƒ, ikun2: ƒ}

从以上代码可得出,在对象obj中的箭头函数ikun1中,输出的this不是当前的对象obj,而是obj的外部window,但在ikun2中,ikun2是正常function定义的函数,所以ikun2输出的this为obj。
2. 箭头函数没有原型和super,不能使用arguments,new.target.

箭头函数适用场景

简单函数表达式,内部没有this引用,没有递归、事件绑定,解绑定。

2.函数名

函数名就是指向函数的指针,所以意味着一个函数可以有多个名称。

 function ikun1(num1,num2){
console.log(num1+num2);
}

ikun1(10,10);//20

let ikun2=ikun1; //声明变量ikun2,初始化为ikun1

ikun2(10,10);//20

ikun1=null;
ikun2(20,20);//40

以上代码定义了一个函数ikun1,又声明了变量ikun2让其值等于ikun1,使用不带括号的函数名会访问函数指针,而不会执行函数。此时,ikun1和ikun2都指向同一个函数,调用ikun2也可以返回结果,将ikun1设置为null,切断了ikun1与函数的联系。ikun2可以正常使用。

3.函数参数

当定义函数时,行参设置为传入2参数,但不意味着实际传入参数时就传2个参数,你可以传入1个、2个、3个、n个,或一个都不传。

之所以这样,因为函数的参数在内部表现为一个数组。函数被调用时会接收一个数组,函数并不关心这个数组中有什么,如果数组中什么也没有,那没问题;如果数组中元素超出了要求,那也没问题。

在使用function定义的函数中,可以在函数内部访问arguments对象,从中取得每个传进来的参数值。

arguments对象是一个类数组对象,但不是Array实例,可以使用arguments[i]来访问第i个传入函数的参数。若要确定穿进来的参数的个数,使用arguments.length

function ikun(){
  console.log(arguments[0]+" "+arguments[1]);
}
ikun("hello","kitty");//hello kitty

通过arguments.length来确定传入到函数的参数的个数。

function ikun(){
  console.log(arguments.length);
}
ikun("hello","kitty","f");//3
ikun();//0
ikun(12);//1

4.函数作为值

函数名就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,还可以在另一个函数中返回另一个函数。

function playIkun(play,ikun){
play(ikun);
}

function add(num){
  return num+10;
}

function greet(name){
  return "hello"+name;
}

let result=playIkun(add,10);//20
let result2=playIkun(greet,"bob");//"hello bob"

说一说call apply bind的作用和区别?

例1:

var name="小王",age=17;
var obj={
  name:"小张",
  objAge:this.age, //这个this指向的是window
  myFun:function(){
    console.log(this.name+"年龄"+this.age);
  }
};

console.log(obj.objAge);//17
obj.myFun();//小张年龄undefined

例2:

var fav="盲僧";
function shows(){
     console.log(this.fav);
}

shows();//盲僧

在例1中,obj.myFun()中输出的this指向的obj,例2中全局声明的shows()函数this是window。

1.call(),apply(),bind()都是用来重新定义this这个对象的。
var obj={
  name:"小张",
  objAge:this.age, //这个this指向的是window
  myFun:function(){
    console.log(this.name+"年龄"+this.age);
  }
};

var db={
  name:"德玛",
  age:99
};

obj.myFun.call(db);//德玛年龄99
obj.myFun.apply(db);//德玛年龄99
obj.myFun.bind(db)();//德玛年龄99

以上例子是将obj的myFun函数的this对象进行改变为db对象。
call(),apply(),bind()函数都是将选定函数的this对象进行改变。

2.call、bind、apply传参情况
var obj={
  name:"小张",
  objAge:this.age, //这个this指向的是window
  myFun:function(fm,t){
    console.log(this.name+"年龄"+this.age,"来自"+fm+"去往"+t);
  }
};

var db={
  name:"德玛",
  age:99
};

obj.myFun.call(db,"成都","上海");//德玛年龄99 来自成都去往上海
obj.myFun.apply(db,["成都","上海"]);//德玛年龄99 来自成都去往上海
obj.myFun.bind(db,["成都","上海"])();//德玛年龄99 来自成都,上海去往undefined

从上面结果可以看出,call的参数是直接放进去的,第二、第三、第n个参数全都用逗号分隔。

apply的所有参数都必须放入到一个数组中传入进去。

bind除了返回的是函数外,它的参数和call一样。

说一说HTML语义化?

什么是语义化?

HTML语义化指在不了解HTML的内容,就可以直到这个标签代表什么意义。
例子:

我们就不知道代表什么
    无序列表
      有序列表
语义化作用
  1. 易于用户阅读,样式文件未加载时,页面结构清晰。
  2. 利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
  3. 方便屏幕阅读器解析,如盲人阅读器根据语义渲染网页。
  4. 有利于开发和维护,代码更具可读性,代码更好维护。

你可能感兴趣的:(前端)