JS 高频面试题

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

基本数据类型(Undefined、Null、Boolean、Number、String、Symbol)
引用数据类型(对象、数组和函数)
区别:
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址;
在操作系统中,内存又被分为栈区和堆区,栈区内存由编译器自动分配释放,堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

Symbol 是什么数据类型

Symbol 是 ES6 引入的一种新的基本数据类型,表示独一无二的值。因为是原始数据类型,所有不能new Symbol。Symbol 类型的 key 不能通过 Object.keys() 或者 for…in 来枚举,它未被包含在对象自身的属性名集合(property names)之中所以,利用该特性,可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。

encodeURI和encodeURIComponent的区别(了解)

应用场景不同:encodeURI是用来处理整个URI的,它应该接受URI的protocol、host、port等部分,只对path和query进行编码。适合给URL本身(locaion.origin)编码。而encodeURIComponent是用来编码准备用作query部分的字符串的。适合给参数编码。
编码范围不同:encodeURI不会对ASCII字母、数字、~!@#$&()=:/,;?+*'进行编码,而encodeURIComponent会对所有非标准字符进行编码。

JS 判断数据类型的方法

方法一、typeof 其中数组、对象、null都会被判断为object,其他判断都正确。

console.log(typeof 'str');           // string
console.log(typeof []);              // object    
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null); 

方法二、instanceof只能正确判断引用数据类型,而不能判断基本数据类型。

console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object); 

方法三、Object.prototype.toString.call()

var a = Object.prototype.toString;
console.log(a.call(2));

判断数组类型的方法有哪些

方法一、
obj.__proto__ === Array.prototype;
方法二、
Object.prototype.toString.call([]);
方法三、
Array.isArrray(obj);
方法四、
obj instanceof Array

为什么使用instanceof不能正确判断基本数据类型

原理是查找左边的_proto_获取到原型对象,判断原型链中是否有和右侧相等;基本数据类型没有构造函数也没有原型链;

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

const 、let、var的区别

一、作用域:let和const具有块级作用域,var不存在块级作用域
二、变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错
三、重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量
四、初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
五、let创建的变量是可以更改指针指向(可以重新赋值),但const声明的变量是不允许改变指针的指向(对象和数组除外)

使用const定义的数据不能修改吗

其实不能改的不是值,而是变量指向的内存地址,对于基本数据类型来说,其值就保存在变量指向的那个内存地址,因此等同于常量。
而对于引用数据类型来说,保存的只是指针,至于指针指向的数据结构是不能保证的,所有引用数据类型是可以修改的;

箭头函数和普通函数的区别

1、普通函数可以使用函数声明和函数表达式定义,箭头 箭头函数语法更简洁
2、箭头函数没有自己的this,它只会在自己作用域的上一层继承this(你不能在箭头函数中使用new关键字来创建一个新的对象实例,因为箭头函数没有自己的this值)
3、箭头函数继承来的this指向永远不会改变,call()、apply()、bind()等方法不能改变箭头函数中this的指向
4、箭头函数没有自己的arguments(普通函数中,arguments 是一个类数组对象,它包含了传递给函数的参数)监听函数中可以只能通过参数名称来访问
5、箭头函数不能作为构造函数使用,因为没有this

JS 模板字面量 是什么

JS 模板字面量是 ES6 引入的一种新的字符串表示方式,它使用反引号(`)包裹字符序列,允许在字符串中插入变量、表达式和原始字符串,并且支持多行文本的处理

如何实现图片懒加载

图片懒加载就是延迟加载非视口的图片,从而提高页面加载速度,提升用户体验。
window.innerHeight 获取视口的高度,图片顶部到窗口的距离可以使用getBoundingClientRect().top 然后监听页面的scroll方法,获取到img标签,如果图片到顶部的距离小于窗口的高度就应该显示图片。

<body>
        <img data-src="/gift.png">
      <script>
        const images = document.querySelectorAll('img');
        window.addEventListener('scroll',()=>{
            images.forEach((item)=>{
                const imageTop = item.getBoundingClientRect().top; // 获取图片到顶部的距离
                if(imageTop < window.innerHeight) {
                const imgSrc =  item.getAttribute('data-src');
                item.setAttribute('src',imgSrc);
                }
            })
        })
      </script>
    </body>

使用这个方法的问题在于只要滚动就会频繁触发,并且已经展示图片的时候还是会执行;
我们可以使用浏览器提高的构造函数IntersectionObserver来优化

 <body>
        <img data-src="/gift.png">
      <script>
        const images = document.querySelectorAll('img'); 
        // 观察的元素每次进入和离开都会触发
        const callBack = entries => {
          entries.forEach(entry => {
            // 有交叉
            if(entry.isIntersecting) {
               const iamge = entry.target;
               const imgSrc =  image.getAttribute('data-src');
               image.setAttribute('src',imgSrc);
               observer.unobserve();// 赋值后取消观察
            }
          })
        }
        const observer = new IntersectionObserver(callBack); 
        // 浏览器提供的构造函数,交叉观察,部分浏览器版本不兼容
        images.forEach((item)=>{
            observer.observe(item);
        })
      </script>
    </body>

说下JS 的事件循环机制

js是单线程的,只有一个调用栈,并且陷入后出;在执行调用栈的时候会先执行同步任务,发现异步任务加入异步任务队列,队列分为宏任务和微任务,队列按照先入先出的规则。
常见的宏任务:script 标签、setTimeout、事件的回调函数
常见的微任务:Promise.then()catch() finally() Object.observer()process.nextTick
执行顺序:
先执行同步任务,清空调用栈,执行微任务队列的任务,微任务队列执行完后才执行宏任务。每次宏任务结束后事件循环会先执行微任务,直到微任务队列里的任务被清空才会执行下一轮宏任务,这样就可以避免因为宏任务的繁重导致任务的阻塞。

JS 中创建对象的方式

1、new Object 实例,为它逐个添加属性和方法,缺点是不便于代码的阅读和管理

let cat = new Object();
cat.name = 'tuotuo';
cat.age= 2;
cat.seepk = function () {
 console.log('我要吃猫条');
}

2、字面量的方式

let obj = {
    key1: 'value1',
    key2: 'value2'
};

3、工厂模式,创建对象模版,缺点是创造的新对象和工厂函数之间缺乏关联

function createCat (name,age) {
 const obj = new Object();
 cat.name = name;
 cat.age= age;
 cat.seepk = function () {
   console.log('我要吃猫条');
 }
 return obj;
}
let cat1 = createCat('xiaoyiyi',0.5);

4、构造函数模式,解决了工厂模式的问题,obj instanceof MyObject 是true

function MyObject(key1, key2) {
    this.key1 = key1;
    this.key2 = key2;
}
let obj = new MyObject('value1', 'value2');

5、Object.create()使用Object.create()方法来创建一个新对象,该方法使用现有的对象来提供新创建的对象的__proto__

let proto = {
    text:'测试1',
    foo: function() { console.log('foo'); }
};
let obj = Object.create(proto);
obj.text = '覆盖文案';
console.log(protp.text) // '覆盖文案'

6、 class 的方式,融合了构造函数和工厂模式

class Cat {
  constructor (name,age) {
    this.name = name;
    this.age =  age;
  }
};
let cat1 = new Cat('tuotuo',2)

说下防抖和节流的区别,并手写

触发频率不同:防抖是将多次执行变为最后一次执行,节流是在一段时间内只执行第一次,时间到了才会执行下一次;
作用不同:防抖用于在一定时间间隔内只触发一次事件,节流用于限制事件在一定时间内的触发次数。
适用场景不同:防抖适用于在用户频繁触发事件时,只执行最后一次事件,如输入框实时搜索;节流适用于在一定时间内只触发一次事件,如滚动加载。
防抖函数实现:

const btn = document.getElementById('btn');
function handleClick () {
  // 因为回调的关系,在防抖函数中调用该this为window
  console.log('点击了按钮')
};  

function  debounce (fn,delay) {
   // 利用闭包将变量放返回函数外层,因为作用域链所有下面返回的函数都能访问到这个变量
   let timer ;
   // 使用高阶函数解决未点击就会被触发的问题
   return function() {
   console.log(this);// 在回调之前该this为btn元素,保存下this执行
   const context = this;
   const args = arguments;// 拿到传递过来的所有参数
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    },delay);
   }
}
btn.addEventListener('click',throttle(handleClick,1000));

节流函数的实现

const btn = document.getElementById('btn');
function handleClick () {
  // 因为回调的关系,在防抖函数中调用该this为window
  console.log('点击了按钮')
};  

function throttle (fn,delay) {
   // 利用闭包将变量放返回函数外层,因为作用域链所有下面返回的函数都能访问到这个变量
   let timer ;
   // 使用高阶函数解决未点击就会被触发的问题
   return function() {
   console.log(this);// 在回调之前该this为btn元素,保存下this执行
   const context = this;
   const args = arguments;// 拿到传递过来的所有参数
   if(timer) return; // 如果定时器不为空说明在时间间隔内不执行任务
    timer = setTimeout(function() {
      fn.apply(context, args);
      timer = null; // 每次执行完了就清空定时器
    },delay);
   }
}
btn.addEventListener('click',throttle(handleClick,1000));

Ajax 有了解吗?

AJAX不是指一种新的编程语言,而是有机地利用了一系列相关的技术,核心是使用 XMLHttpRequest 进行异步数据查询、检索,通过这个对象 可在不重载页面的情况与 Web 服务器交换数据,再使用 DOM(Document Object Model)进行动态显示及交互。

XML被设计用来传输和存储数据,早期的时候使用的是XML,现在使用JSON 格式;

优点:
1.无刷新更新数据
Ajax最大的优点就是能在不刷新整个页面的情况下维持与服务器通信
2.基于规范被广泛支持
不需要下载浏览器插件或者小程序,但需要客户允许JavaScript在浏览器上执行。
缺点:
1.Ajax干掉了Back与History功能,即对浏览器机制的破坏
在动态更新页面的情况下,用户无法回到前一页的页面状态,因为浏览器仅能记忆历史纪录中的静态页面
2.只能同源请求
AJAX只能向同源的地址发送请求,如果跨源(非同源地址)发送请求,会受同源策略的影响,无法得到响应结果(同源是指两个页面拥有相同的协议、域名和端口,如果有一个不相同,就是不同源)
3.网页内容动态获取,对SEO不友好

http请求和响应报文,都包含四个部分:行 头 空行 体

ajax浏览器走缓存未发送请求?
可以在请求的url上拼一个时间戳,这样每次url是不一样的,都会发起请求

ajax 用户重复点击重复发请求如何优化?
可以添加一个loading标识。如果上一个请求未完成,则abort取消上一个请求,创建的新的xhr对象并发送请求。

说说你对跨域的理解

在解释跨域之前,先解释下同源策略,同源策略是了浏览器的安全策略,他会检查请求的源和响应脚本的源是否相同,如果不相同,那么服务端的响应会被浏览器拦截,并且在控制台抛出错误,而实际请求是发送成功的,响应也可能是成功的;如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
在实际开发中,我们是需要请求不同源的数据的,所以需要绕开浏览器的这个限制。
解决方案:
一、JSONP(JSON with Padding):核心就是利用

你可能感兴趣的:(前端面经&面试题,javascript,前端,开发语言)