私藏前端八股收集【JS篇】

目录

1、JS的数据类型有哪八种

2、dom

3、重排、重绘

4、Promise

Promise的实例方法拓展

5、实现类new功能函数

6、原型链

7、JS如何实现继承(原型链扩展)

8、ES6新特性

(1)let和const

(2)proxy(vue3响应式)

(3)箭头函数

(4)字符串新方法

(5)解构

(6)对象方法

(7)class

9、闭包

10、如何获取对象类型

11、如何判断 this 指向

12、bind、call、apply的区别(改变普通函数的this指向)

1、JS的数据类型有哪八种

基本(简单)数据类型:Number、String、Boolean、BigInt(ES6新类型)、Symbol、Null、Undefined

特点:

直接存储在栈(由操作系统自动分配释放)中的简单数据段,占据空间小,属于被频繁使用的数据,赋值时直接生成相同的值,地址不同。

Symbol:

ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key,为对象添加唯一属性。

let key = Symbol('key');
let obj = { [key]: 'symbol'};
let keyArray = Object.getOwnPropertySymbols(obj); // 返回一个数组[Symbol('key')]
obj[keyArray[0]] // 'symbol'

引用(复杂)数据类型:Object(普通对象,数组,正则,日期,Math数学函数)

特点:

存储在堆(一般由程序员分配释放)内存中,占据空间大,栈中存放指向堆内存的地址,所以赋值时相当于赋指针。

2、如何判断一个对象为空对象

let obj = {};
// Reflect.ownKeys方法返回一个由目标对象自身的属性组成的数组
// =Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
console.log(Reflect.ownKeys(obj).length === 0);
// undefined、函数以及 symbol 会出错
console.log(JSON.stringify(obj) === "{}");
// 会返回对象自身可枚举属性组成的数组,而不会遍历原型上的属性,Symbol不可用
console.log(Object.keys(obj).length === 0);
// 可以得到对象自身的所有属性名组成的数组,Symbol不可用
console.log(Object.getOwnPropertyNames(obj).length == 0);
// for in 循环判断,Symbol不可用
function isEmptyObj(obj) {
  for (let item in obj) {
    return true;
  }
  return false;
}
console.log("对象是否为空:", isEmptyObj({}));

// 不可枚举属性
Object.defineProperty(obj, "a", {
  value: 1,
  enumerable: false,
});

  3、浅拷贝深拷贝

(1)概念区别

浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了 “指向堆内存的地址” 。

深拷贝:深拷贝是拷贝多层,每一级别的数据都会拷贝出来。

(2)实现

浅拷贝:

// 该方法可以用于JS 对象的合并,不能拷贝对象的继承和不可枚举属性
Object.assign(newObj, obj)
// 扩展运算符,缺陷同上
let newObj = { …obj };
// 数组是被浅拷贝复制了,但如果里面有其他元素比如对象,那就是复制指针了
let newObj = obj.concat()
// 同上
let newObj = obj.slice(begin, end);

 深拷贝:

// 函数、undefined、symbol不适用,原型链无法拷贝
let b = JSON.parse(JSON.stringify(a))
// 递归复制
function deepCopyTwo(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj == 'object') {
         for (const key in obj) {
             //判断obj子元素是否为对象,如果是,递归复制
             if (obj[key] && typeof obj[key] === "object") {
                 objClone[key] = deepCopyTwo(obj[key]);
             } else {
                 //如果不是,简单复制
                 objClone[key] = obj[key];
             }
         }
     }	
     return objClone;
 }
// lodash是一个js原生库
let _ = require('lodash');
let newObj = _.cloneDeep(obj);

4、dom

DOM是JS操作网页的接口,全称为“文档对象模型”(Document Object Model)。浏览器会根据DOM模型,将结构化文档(比如HTML和XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口,从而可以用脚本进行各种操作(比如增删内容)。

var x1=document.getElementById("main");                 // 通过 id 查找 HTML 元素

var x2=x1.getElementsByTagName("p");                     // 通过标签名查找 HTML 元素集合

var myNodelist = document.querySelectorAll("p").length; // 获取 <p> 元素的集合长度

var x3=document.getElementsByClassName("");        // 通过类名找到 HTML 元素

document.write("");                                                       // 向 HTML 输出流写内容(覆盖文档)

document.getElementById("").innerHTML="";              // 改变 HTML 内容

document.getElementById("").style.color="blue";         // 改变 HTML 样式

<body οnlοad="" οnunlοad="">                                     // 进入或离开页面时被触发

- onchange:当用户改变输入字段的内容时触发,常结合对输入字段的验证来使用。

- onmouseover 和 onmouseout :在用户的鼠标移至 HTML 元素上方或移出元素时触发。

- 点击鼠标按钮时,会触发onmousedown事件;当释放鼠标按钮时,会触发onmouseup事件;当完成鼠标点击时,会触发onclick事件

5、重排、重绘

重排:

        浏览器渲染页面是基于流式布局的,对某一个DOM节点信息进行修改时,需要对该DOM结构进行重新计算。该DOM结构的修改会决定周边DOM结构的更改范围,主要分为全局范围(整个渲染树)和局部范围(渲染树的某部分)。

        浏览器会维护一个重排操作队列,等队列中的操作达到了一定的数量或者一定的时间间隔时,浏览器才会去刷新一次队列,进行真正的重排操作。

常见操作:

页面首次渲染。

浏览器窗口大小发生改变。

元素大小、形状、位置改变。

获取一些样式属性和函数。

重绘:

重绘只是改变元素在页面中的样式,而不会引起元素在文档流中位置的改变,例如字体颜色。重排一定会引起重绘的操作,而重绘不一定会引起重排的操作。

6、Promise

Promise 是一个 ES6 提供的类,目的是更加优雅地书写复杂的异步任务,能够解决回调地狱问题,比如下面这种多次调用异步任务造成的嵌套 "函数瀑布"。

setTimeout(function () {
    console.log("First");
    setTimeout(function () {
        console.log("Second");
        setTimeout(function () {
            console.log("Third");
        }, 3000);
    }, 4000);
}, 1000);

现在我们用 Promise 来实现同样的功能:

// 新建,然后,然后。。。
new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve(); // 异步操作执行成功后的回调函数
    }, 1000);
}).then(function () {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("Second");
            resolve();
        }, 4000);
    });
}).then(function () {
    setTimeout(function () {
        console.log("Third");
        reject('执行失败'); // 异步操作执行失败后的回调函数
        throw "err";        // return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
    }, 3000);
}).catch(function (err) {   // catch 块只会执行第一个
    console.log(err);
}).finally(function () {    // finally 与 then 一样会按顺序执行
    console.log("End");
});

 简化一下就成了:

function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

Promise 不是一种将异步转换为同步的方法,只不过是一种更良好的编程风格。

为什么这样说呢?因为更简单的写法如下:

async function asyncFunc() {
    // 在异步函数中说“啊,等等”,于是它们排起队
    await print(1000, "First");
    await print(4000, "Second");
    await print(3000, "Third");
    try {
        await new Promise(function (resolve, reject) {
            throw "Some error"; // 或者 reject("Some error")
        });
    } catch (err) {
        console.log(err);
    }
}
asyncFunc();

Promise 的三种状态:pending (等待态)、fulfiled (成功态)、rejected (失败态)

特点:状态改变之后就不会再变;承诺结果不会受外部影响。

缺点:

1)无法取消Promise,一旦新建它就会立即执行,无法中途取消,执行状态不可逆;
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部;
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成。

Promise 本身是同步的,Promise 的回调then、catch是异步。

then()状态变化触发,catch()发生错误触发,finally() 最后必然执行的操作。

Promise的实例方法拓展

  • p = Promise.all([ ]) :只要数组中有一个状态为 rejected,p 的状态就变成 rejected,第一个被reject的实例,返回值会传递给p的回调函数。
  • Promise.race([ ]):数组中谁先改变状态, p 也就会跟着改变状态。率先改变的会将返回值传递给 p 的回调函数。
  • Promise.allSettled():用来确定一组异步操作是否都结束了(不管成功或失败)。
  • Promise.any():ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

7、实现类new功能函数

function myNew(Fn, ...args){
    // 创建一个空对象,作为此函数返回的对象实例
    const obj = {}
    // 将创建的空对象原型指向构造函数的prototype属性
    obj.__proto__ = Fn.prototype
    // 同时将这个空对象赋值给构造函数内部的this
    const res = Fn.call(obj, ...args)
    // 返回初始创建的对象或构造函数的显式返回值
    return ['object', 'function'].includes(typeof res) ? res : obj
}

8、原型链

  1. 每个函数都有一个 prototype 指向自己的原型对象,即显示原型。
  2. 每个通过函数创建出来的实例对象都有一个__proto__,可称为隐式原型,这个属性指向当前对象的构造函数的原型对象。
  3. 对于原型对象来说,它有个constructor属性,指向它的构造函数。
  4. 原型对象的构造函数是Object(),而Object.prototype没有上一层的原型对象。
  5. 函数对象的构造函数是Function() ,而 Function()本身也是函数,所以Function() 是自己的实例。

9、JS如何实现继承(原型链扩展)

(1)使用class语法,用extends进行继承

class Car {
  constructor(brand) {
    this.brand = brand;
  }
  showBrand() {
    console.log("the brand of car :>> ", this.brand);
  }
}

// 其实跟java很像吧阿巴阿巴
class ElectricCar extends Car {
    // 啊哈哈哈重载了
  constructor(brand, duration) {
    super(brand);
    this.duration = duration;
  }
  showDuration() {
    console.log(`duration of this ${this.brand} ElectricCar :>> `, this.duration);
  }
}

// 给原型对象Car{}加个方法
ElectricCar.prototype.showOriginator = function (originator) {
  console.log(`originator of this ElectricCar :>> `, originator);
};

const tesla = new ElectricCar("tesla", "600km");
tesla.showBrand(); // the brand of car :>>  tesla
tesla.showDuration(); // duration of this tesla ElectricCar :>>  600km
// instanceof用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
console.log("tesla instanceof Car :>> ", tesla instanceof Car); // tesla instanceof Car :>>  true
console.log("tesla instanceof ElectricCar :>> ", tesla instanceof ElectricCar); // tesla instanceof ElectricCar :>>  true
console.log("tesla.__proto__ :>> ", tesla.__proto__); // tesla.__proto__ :>>  Car {}
console.log("ElectricCar.prototype === tesla.__proto__  :>> ", ElectricCar.prototype === tesla.__proto__); // ElectricCar.prototype === tesla.__proto__  :>>  true
tesla.showOriginator("Mask"); // originator of this  ElectricCar :>>  Mask

(2)直接改变对象的__proto__指向

const bydCar = {
  brand: "比亚迪",
  duration: "666km",
};
bydCar.__proto__ = ElectricCar.prototype;

bydCar.showBrand(); //the brand of car :>>  比亚迪

10、ES6新特性

(1)let和const

let、const、var的区别:

  1. let和const声明块级作用域({});var声明函数作用域(fn(){}),所以let和const只能在块级作用域里访问,如果使用if(){let a=10},在if外面console会报错找不到对象。
  2. let和const没有变量提升,var有(比如我在console之后才用var定义变量,那么console会输出undefined,但是用let和const定义就会报错找不到变量)。
  3. let和const不能重复声明,var可以。
  4. const定义常量,不能修改,let可以。
  5. var 和 let 可以不设置初始值,const 声明变量必须设置初始值

(2)proxy(vue3响应式)

(3)箭头函数

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

  1. 箭头函数不会创建自身的this,只会从上一级继承this,箭头函数的this在定义的时候就已经确认了,之后不会改变。
  2. 箭头函数无法作为构造函数使用,没有自身的prototype,也没有arguments。

(4)字符串新方法

  • includes()判断字符串是否包含参数字符串,返回boolean值。
  • startsWith() / endsWith(),判断字符串是否以参数字符串开头或结尾。返回boolean值。这两个方法可以有第二个参数,一个数字,表示开始查找的位置。
  • repeat()方法按指定次数复制返回一个新的字符串。
  • padStart()/padEnd(),用参数字符串按给定长度从前面或后面补全字符串,返回新字符串。
  • arr.filter(function (item) {return item > 2}),返回过滤值。

  • every(function (item,index,arr) {}),是否满足所有条件,返回布尔值。

  • some(function (item,index,arr) {}) ,数组中有没有满足条件的,返回布尔值。

  • find(function (item,index,arr) {}),返回数组中满足条件的第一个数据。

(5)解构

// 数组解构
let [a,b,c] = [1,2,3];
console.log(a,b,c);    //1,2,3

// 对象解构
let obj = { 
	name: "ren", 
	age: 12, 
	sex: "male" 
};
let { name, age, sex } = obj;
console.log(name, age, sex); //'ren' 12 'male'

(6)map和foreach的区别

Map对象用于保存键值对,forEach用来循环遍历数组

// 数组的forEach和map方法有哪些区别

const arr = [1, 2, 3, 4, 5, 6];

// forEach是对数组的每一个元素执行一次给定的函数。
arr.forEach(x => {
  x = x + 1;
  console.log("x :>> ", x);
});

// map是创建一个新数组,该新数组由原数组的每个元素都调用一次提供的函数返回的值。
const mapArr = arr.map(x => {
  x = x * 2;
  return x;
});
console.log("mapArr :>> ", mapArr);

(7) 数组方法

改变原数组的方法: 

  1. pop():删除尾部元素,返回被删元素
  2. push():将一个元素或多个元素添加到数组末尾,返回新长度
  3. shift():删除头部元素,返回被删元素
  4. unshift():将一个或多个元素添加到数组的开头,返回新长度
  5. splice():截取数组arr.splice(1,2);删除并插入数据:数组名.splice(开始索引,多少个,你要插入的数据),返回被删元素
  6. reverse(): 反转数组。
  7. sort():升序:arr.sort(function(a,b){return(a-b)});降序:arr.sort(function(a,b){return(b-a)})

不改变原数组的方法:

  • concat():拼接数组。
  • join('连接符'):数组拼接成字符串。
  • slice(开始下标,结束下标):截取一段,返回截取值(包含开始下标不包含结束下标)。
  • indexOf:arr.indexOf(10):从左检查数组中有没有这个数值,返回该数据第一次出现的下标;arr.indexOf(10,n):返回10第n次出现的下标。

  • lastIndexOf():同上相反

(8)数组去重 

Set对象和Map对象类似,但它存储不是键值对。类似数组,但它的每个元素都是唯一的

// 利用set给数组去重
var list = [
  1,2,1,{name:1},{name:1},null, NaN,0,0,{},'','',[1],[1],null, undefined, 
  false,9, undefined,'true','false','true'
]
console.log(Array.from(new Set(list)))
console.log([...new Set(list)])

 11、闭包

闭包让开发者可以从内部函数访问外部函数的作用域。如果一个函数访问了此函数的父级及父级以上的作用域变量,就可以称这个函数是一个闭包。常见的闭包使用有两种场景:一种是函数作为参数被传递;一种是函数作为返回值被返回。

(1)闭包实现累加器

function add() {
	var count = 0;
    function demo() {
		count++;
		console.log(count);
	}
	return demo;
}
var counter = add();
counter();
counter();
counter(); 
			

(2)易出现的bug

var elements = document.getElementsByTagName('li');
for (var i = 0; i < elements.length; i++) {
  elements[i].onclick = function () {
    console.log(i);    // 会输出44444……
    alert(i);
  };
}

// 每个li标签的onclick事件执行时,本身onclick绑定的function的作用域中没有变量i
// i为undefined,则解析引擎会寻找父级作用域,发现父级作用域中有i
// 且for循环绑定事件结束后,i已经赋值为4
// 所以每个li标签的onclick事件执行时,alert的都是父作用域中的i,也就是4。
// 这是作用域的问题。这里onclick事件绑定的函数形成闭包,但引用的变量i是全局的。

for (let i = 0; i < elements.length; i++) {    // 这样修改
  elements[i].onclick = function () {
    return (function (n) {
      console.log(n);
      alert(n);
    })(i);
  };
}

(3)内存泄漏

因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏。

解决方法:在退出函数之前,将不使用的局部变量全部删除,可以使变量赋值为 null。

12、如何获取对象类型

  1. Object.prototype.toString.call(obj)
  2. constructor(Object.constructor())
  3. typeof 缺点:判断引用类型时,比如typeof {}全返回object
  4. [] instanceof Array 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。缺点:不是通过实例化得到的对象就无法判断,比如console.log('哈哈' instanceof String) 输出 false

13、如何判断 this 指向

14、bind、call、apply的区别(改变普通函数的this指向)

参数不同:

call(this,arg1,arg2…)

apply(this,[arg1,arg2,…])

bind(this,[arg1,arg2,..])
函数执行不同:

call,apply方法之后调用后,函数立即执行。

bind方法调用后,返回了一个改变this后的函数,不会立即执行。

被bind绑定过this的函数,this不会再被改变。

常见应用:
1.判断数据类型:Object.prototype.toString.call(null) //[object Null ]

2.获取函数最大值和最小值:Math.max.apply(Math,[1,2,3]) 

3.伪数组转真数组:Array.prototype.slice.call(arguments)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值