JS常见问题点

本文详细探讨了JavaScript中的关键概念,包括数据类型、原型和原型链、this指向、执行上下文、变量声明、闭包以及异步处理中的宏任务和微任务。此外,还涉及了XSS攻击、事件代理和JSONP等前端开发中的重要知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据类型

JS 的基本数据类型有6种:Number、String、Boolean、Undefined、Null、Symbol(ES6新增)
JS引用数据类型:Object(Object、Function、Array)

  1. 类型区别
  • 基本类型数据存储在栈中,引用类型数据存储在堆中,栈中存储的变量,是指向堆中的引用地址;
  • 基本类型数据按值访问,引用类型数据按引用访问;
  • 复制变量不同;
  • 函数传参都是按值传递,基本类型数据拷贝的是值,引用类型数据拷贝的是引用地址
  1. 类型判断:

typeof

  • 除了null外都显示正确的类型(JS最初版本32位系统原因);
  • 引用类型除了函数都返回 ‘object’ ;

Object.prototype.toString.call(xx)

Object.prototype.toString.call(1)  // "[object Number]"
  1. 类型转换
  • Boolean:在条件判断时,除了 undefined , null , false , NaN , ‘’ , 0 , -0 ,其他所有值都转为 true ,包括所有对象;
  • 对象转基本类型

原型和原型链

我们创建的每个函数都有 prototype 属性,这个属性指向函数的原型对象。原型对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
在默认情况下,所有原型对象都会自动获得一个== constructor ==属性,这个属性包含一个指向 prototype 属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象(可以通过实例的__proto__ 来访问构造函数的原型对象)。

  1. 实例.proto === 构造函数.prototype

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个可以指向原型对象的内部指针(可以通过 proto 访问)。
对象可以通过 proto 来寻找不属于该对象的属性, proto 将对象连接起来组成了原型链。

  1. new 的过程
    新生成一个对象 - 链接到原型 - 绑定this - 返回新对象

  2. instanceof
    A instanceof B:如果B函数的显式原型对象在A对象的原型链上,则返回true,否则返回false。

this指向问题

谁调用它,this就指向谁

  • ES6箭头函数:this指向上下文绑定的this(箭头函数无this!!!
  • 全局环境下,this指向window
  • new对象中,this指向新对象
  • callapplybind调用时,this指向的就是指定对象(常用于更改this指向)

JS 执行上下文栈和作用域以及作用域链

JS执行上下文过程中,需要创建变量对象,创建作用域链,确定this的值,从而实现下一步动作。

  1. 类型:
    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文
  2. 变量提升和函数提升
    生成上下文时,显示创建/定义阶段,开辟内存空间,函数是整个存入,变量只是声明且复制undefined;函数优先于变量提升哦~
  • 作用域
    全局、块级、函数三类作用域环境。
  • 作用域链
    从当前作用域层层向上寻找某个变量,知道找到全局作用域,就结束。这种层层关系即被成为作用域链。

var、let、const

  • let/const 定义的变量不会出现变量提升,而 var 定义的变量会提升。即let/const有暂时性死区的问题,let/const 声明的变量,在定义之前都是不可用的。如果使用会抛出错误。;
  • 相同作用域中,let 和 const 不允许重复声明,var 允许重复声明;
  • const 声明变量时必须设置初始值;
  • const 声明一个只读的常量,这个常量不可改变;
  • let/const 声明的变量仅在块级作用域中有效。而 var 声明的变量在块级作用域外仍能访问到;

闭包

闭包是指有权访问另一个函数作用域内的变量的函数。创建闭包最常用的方式就是创建一个函数(函数 A 返回了⼀个函数 B,并且函数 B 中使⽤了函数 A 的变量,函数 B就被称为闭包)。
简单说来:函数+变量=闭包

function foo() {
    var a = 2;
    return function fn() {
        console.log(a);
    }
}
let func = foo();
func(); //输出2

闭包作用

  • 能有访问函数定义时所在的作用域(防止被回收);
  • 私有化变量
  • 模拟块级作用域
  • 创建模块

** 闭包缺点**
- 闭包容易造成内存泄漏(因为 IE。IE 有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。)

浅拷贝、深拷贝

拷贝其实就是赋值操作。常见的深拷贝方法

  • JSON.parse(JSON.stringfly(obj))
  • 递归赋值
function deepClone(obj, hash = new WeakMap()) { //递归拷贝
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(obj === null || typeof obj !== 'object') {
        //如果不是复杂数据类型,直接返回
        return obj;
    }
    if (hash.has(obj)) {
        return hash.get(obj);
    }
    /**
     * 如果obj是数组,那么 obj.constructor 是 [Function: Array]
     * 如果obj是对象,那么 obj.constructor 是 [Function: Object]
     */
    let t = new obj.constructor();
    hash.set(obj, t);
    for(let key in obj) {
        //如果 obj[key] 是复杂数据类型,递归
        if(obj.hasOwnProperty(key)){//是否是自身的属性
            if(obj[key] && typeof obj[key] === 'object') {
                t[key] = deepClone(obj[key], hash);
            }else{
                t[key] = obj[key];
            }
            
        }
    }
    return t;
}

防抖和节流

防抖:当时间触发n秒后执行回调。如果n秒内又被触发,则重新计时;常用于按钮提交、搜索框联想等;(立即执行、非立即执行)
节流:规定在一定时间内只能被触发一次函数。规定时间内多次触发,只执行一次。常用于拖拽、缩放场景;(时间错实现、定时器实现)

// 防抖
// 这个是⽤来获取当前时间戳的
function now() {
 return +new Date()
}
/**
 * 防抖函数,返回函数连续调⽤时,空闲时间必须⼤于或等于 wait,func 才会执⾏
 *
 * @param {function} func 回调函数
 * @param {number} wait 表示时间窗⼝的间隔
 * @param {boolean} immediate 设置为ture时,是否⽴即调⽤函数
 * @return {function} 返回客户调⽤函数
 */
function debounce (func, wait = 50, immediate = true) {
	 let timer, context, args
	
	 // 延迟执⾏函数
	 const later = () => setTimeout(() => {
		 // 延迟函数执⾏完毕,清空缓存的定时器序号
		 timer = null
		 // 延迟执⾏的情况下,函数会在延迟函数中执⾏
		 // 使⽤到之前缓存的参数和上下⽂
		 if (!immediate) {
			func.apply(context, args)
		 		context = args = null
		 }
	 }, wait)
	 // 这⾥返回的函数是每次实际调⽤的函数
	 return function(...params) {
		 // 如果没有创建延迟执⾏函数(later),就创建⼀个
		 if (!timer) {
		 	timer = later()
			 // 如果是⽴即执⾏,调⽤函数
			 // 否则缓存参数和调⽤上下⽂
			 if (immediate) {
			 	func.apply(this, params)
			 } else {
			 	context = this
			 	args = params
			 }
		 // 如果已有延迟执⾏函数(later),调⽤的时候清除原来的并重新设定⼀个
		 // 这样做延迟函数会重新计时
		 } else {
		 	clearTimeout(timer)
		 	timer = later()
		 }
	 }
}

/**
 * underscore 节流函数,返回函数连续调⽤时,func 执⾏频率限定为 次 / wait
 *
 * @param {function} func 回调函数
 * @param {number} wait 表示时间窗⼝的间隔
 * @param {object} options 如果想忽略开始函数的的调⽤,传⼊{leading:
false}。
 * 如果想忽略结尾函数的调⽤,传⼊{trailing:
false}
 * 两者不能共存,否则函数不能执⾏
 * @return {function} 返回客户调⽤函数
 */
 _.throttle = function (func, wait, options) {
        var context, args, result;
        var timeout = null;
        // 之前的时间戳
        var previous = 0;
        // 如果 options 没传则设为空对象
        if (!options) options = {};
        // 定时器回调函数
        var later = function () {
          // 如果设置了 leading,就将 previous 设为 0
          // ⽤于下⾯函数的第⼀个 if 判断
          previous = options.leading === false ? 0 : _.now();
          // 置空⼀是为了防⽌内存泄漏,⼆是为了下⾯的定时器判断
          timeout = null;
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        };
        return function () {
          // 获得当前时间戳
          var now = _.now();
          // ⾸次进⼊前者肯定为 true
          // 如果需要第⼀次不执⾏函数
          // 就将上次时间戳设为当前的
          // 这样在接下来计算 remaining 的值时会⼤于0
          if (!previous && options.leading === false) previous = now;
          // 计算剩余时间
          var remaining = wait - (now - previous);
          context = this;
          args = arguments;
          // 如果当前调⽤已经⼤于上次调⽤时间 + wait
          // 或者⽤户⼿动调了时间
          // 如果设置了 trailing,只会进⼊这个条件
          // 如果没有设置 leading,那么第⼀次会进⼊这个条件
          // 还有⼀点,你可能会觉得开启了定时器那么应该不会进⼊这个 if 条件了
          // 其实还是会进⼊的,因为定时器的延时
          // 并不是准确的时间,很可能你设置了2秒
          // 但是他需要2.2秒才触发,这时候就会进⼊这个条件
          if (remaining <= 0 || remaining > wait) {
            // 继承在ES5中,我们可以使⽤如下⽅式解决继承的问题
            // 如果存在定时器就清理掉否则会调⽤⼆次回调
            if (timeout) {
              clearTimeout(timeout);
              timeout = null;
            }
            previous = now;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
          } else if (!timeout && options.trailing !== false) {
            // 判断是否设置了定时器和 trailing
            // 没有的话就开启⼀个定时器
            // 并且不能不能同时设置 leading 和 trailing
            timeout = setTimeout(later, remaining);
          }
          return result;
        };
      };

XSS攻击 - 代码(跨站脚本)注入攻击

事件代理(事件委托)

DOM事件事件流:捕获阶段、目标阶段、冒泡阶段;
实现:addEventListener(type, listener[, options])

宏任务和微任务(event loop)

Event loop :单线程是js的一大特点,Event loop即是处理协调事件、防止线程阻塞等问题的方案。Event loop包含 基于Browsing和基于worker两类。

宏任务
(macro)task:每次执行栈执行的代码就是一个宏任务;包括:script代码、setTimeout、UI交互、postMessage等
微任务
(micro)task:当前宏任务执行结束后立即执行的任务,即在渲染之前,下一个宏任务之前;包括:Promise.then()、Object。observe()、process.nextTick()等

JS运行机制

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

setTimeout只能保证延时时间或者间隔时间不小于设定时间,存在延时的原因就因为该问题;

异步加载js的方法

  • <script> 标签中增加 async(html5) 或者 defer(html4) 属性,脚本就会异步加载。
  • 动态创建**<script>** 标签
  • XHR 异步加载JS

数组扁平化

function flattenDeep(arr){
    return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
console.log(flattenDeep([1, [2, [3, [4]], 5]]));

JSONP原理和简单实现

尽管浏览器有同源策略,但是 <script> 标签的src属性不会被同源策略约束,可以获取任意服务器上的脚本并且执行。jsonp通过插入**<script>** 的方法来实现跨域,参数通过url传入,所以仅支持get请求。

function jsonp({url, params, callback}) {
    return new Promise((resolve, reject) => {
        //创建script标签
        let script = document.createElement('script');
        //将回调函数挂在 window 上
        window[callback] = function(data) {
            resolve(data);
            //代码执行后,删除插入的script标签
            document.body.removeChild(script);
        }
        //回调函数加在请求地址上
        params = {...params, callback} //wb=b&callback=show
        let arrs = [];
        for(let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
    });
}

function show(data) {
    console.log(data);
}
jsonp({
    url: 'http://localhost:3000/show',
    params: {
        //code
    },
    callback: 'show'
}).then(data => {
    console.log(data);
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值