js相关

92 阅读18分钟

JS相关

1.说说JavaScript中的数据类型?存储上的差别?

包括基本类型和复杂类型。

基本类型6大类--NNSBUS

  • Number
  • String
  • Boolean
  • Undefined
  • null
  • symbol

复杂类型也叫引用类型统称object,主要包括3种:

  • Object
  • Array
  • Function

区别:基本数据类型存储在栈中,引用类型的对象存储在堆中。

基本类型的赋值操作就是将原内存地址里的数据在栈中再复制一份放到另一个内存地址里;而引用类型在栈中保存的不是内存地址而是一个引用地址,指向的是堆内存中的一个对象,引用类型的赋值操作是复制引用地址,两者指向的是同一个堆内存对象;基本类型对赋值后的变量修改不会影响原变量,而引用类型对赋值后的对象修改直接影响的同一个对象。

2.数组的常用方法有哪些?

2.1操作方法

数组的基本操作可以归纳为增、删、改、查。

包括:

  • push()(栈方法)(队列方法-末尾添加数据)
  • unshift()
  • splice()
  • concat()(操作方法)

push()接收任意数量的参数,并将其添加到数组末尾,返回最新的长度 --改变了原数组

unshift()数组开头添加任意多个值,返回最新的长度 --改变了原数组

splice()需要传入三个参数,分别是开始位置、删除的元素个数、插入的元素,返回的是被删除的元素的数组--改变了原数组 (第二个值传0原数组实现了新增元素)

concat()会创建一个新的数组,并将参数添加到数组末尾,返回这个新的数组--不会影响原数组

并且concat方法会自动使数组扁平化。可以把要加入的数组添加一个属性[Symbol.isConcatSpreadable],值设置为false就不会自动扁平化了。

包括:

  • pop()(栈方法)
  • shift()(队列方法-开头删除数据,并获取该数据)
  • splice()
  • slice()(操作方法)

pop()删除数组最后一个,返回被删除的元素 --改变了原数组

shift()删除数组第一个,返回被删除的元素 --改变了原数组

!!! splice()传入三个参数,第一个参数为开始位置,第二个参数为删除元素的个数,返回被删除元素的数组 --改变了原数组

let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // [green,blue]
console.log(removed); // red,只有一个元素的数组

slice()传入两个参数,即开始的索引和结束的索引,返回拆分的数组,包括开始不包括结束。 --不改变原数组

let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
console.log(colors)   // [red,green,blue,yellow,purple]
console.log(colors2); // [green,blue,yellow,purple]
console.log(colors3); // [green,blue,yellow]

包括:

splice()传入三个参数,分别是开始位置、删除数量、要插入的一个或多个元素,返回删除元素的数组 ---改变了原数组

let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // red,red,purple,blue
console.log(removed); // green,只有一个元素的数组

包括:

  • indexOf()
  • includes()
  • find()

indexof()返回要查找的元素在数组的位置,没找到则返回-1

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.indexOf(4) // 3

includes()返回数组是否包含所查元素,找到返回true,没找到返回false

find()返回满足条件的第一个元素。

总结:

splice()可以同时实现增、删、改的功能,传入的三个参数分别为开始位置、删除数量、要插入的一个或多个元素,返回的永远是删除的元素,没有就返回空数组。pushpop在数组末尾操作,unshift(添加)和shift在开头操作;concat不会影响原数组,末尾添加一个元素并返回一个新数组;slice也不会影响原数组,从原数组中裁剪。

2.2排序方法

对元素重新排序一般有两种方法:

  • reverse()
  • sort()

reverse()就是将数组元素顺序反转。

sort()会接收一个比较函数 ,用于判断哪个值应该在前面

function compare(value1, value2) {
    if (value1 < value2) {
        return -1 
    } else if (value1 > value2) {
        return 1
    } else {
        return 0
    }
}
//function compare2(value1, value2) {
//    return value1 - value2 //从小到大排序
//}
const values = [0, 5, 1, 10, 9]
values.sort(compare)
console.log(values) // 0,1,5,9,10

2.3转换方法

join()

join()接收一个参数,即字符串分隔符,返回包含所有项的字符串

let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
alert(colors.toString()); // red,green,blue
alert(colors.valueOf()); // red,green,blue

valueOf()返回对象的原始值。

console.log(("1").valueOf());//1   undefined和null都会报错
console.log(({p:1}).valueOf());//{p:1}  如果不是原始类型值,那么不做处理原路返回
console.log([1,2,3,4].valueOf());//[1,2,3,4],原始值还是数组

toString()返回对象的字符串

console.log(("1").toString());//1    undefined和null都会报错
console.log(({p:1}).toString());//[object,object]
console.log([1,2,3,4].toString());//1,2,3,4

toLocaleString()

和上面两个类似。?

2.4迭代方法

常用来迭代数组的方法有如下:(都不改变原数组

  • some()
  • every()
  • forEach()
  • filter()
  • map()

some和every类似,都传入一个函数,前者只要有一项满足条件,则返回true,后者全部满足条件则返回true

forEach()传入一个函数,对数组进行某些操作,没有返回值(for...of用来遍历可迭代对象,拿到的是key和value;for...in用来遍历对象,拿到的是key)

filter()对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3,4,5,4,3]

map()传入一个函数,返回每个元素调用函数的结果构成的新数组

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult) // [2,4,6,8,10,8,6,4,2]

2.5 Array构造函数方法

  • Array.from(arr,fn)

将传入的arr类数组对象(任意可迭代的结构,或者有一个length属性和可索引元素的结构)转为真正的数组,并返回一个新数组。

也可以传入第二个参数,类似map的原理,对输入的数组进行处理,并返回一个新数组。Array.from(arr,fn) = Array.from(arr).map(fn)

  • Array.of(args)

把一组参数转换为数组,类似于ES5的Array.prototype.slice.call(args),太繁琐。

2.6迭代器方法

  • arr.keys()获取数组的键
  • arr.values()
  • arr.entries()

可以用ES6的数组解构来拆分键值对:

for(const [index, element] of arr.entries()) {
	console.log(index)
	console.log(element)
}

2.7复制和填充方法

  • arr.fill()

第一个参数为要向数组传入的值,第二个参数为要开始添加元素的位置索引,第三个参数为结束的索引,没有则一直添加到末尾。

只传一个参数的话就是把整个数组都设置为该参数。

  • arr.copyWithin()

从arr中复制从传入的第一个参数的索引开始的内容,插入到传入的第二个参数的索引的位置。三个参数的话就是后两个参数一个为开始索引一个为结束索引。

如果只传一个参数,那么就是整个arr插入到该参数的索引的位置。

2.8搜索方法

严格相等: 必须要与传入的参数严格相等(===)

  • arr.indexOf()

从数组前面开始搜索,返回找到的索引,找不到就返回-1

  • arr.lastIndexOf()

从数组后面开始搜索

  • arr.includes()

返回布尔值,是否含有传入的参数的元素

断言函数: 传入一个断言函数,该函数接受三个参数:元素、索引、数组本身,也可以再传入一个参数,表示断言函数内部的this的值

  • arr.find()
  • arr.findIndex()
const people = [
    {
        name: 'fan',
        age: 29
    },
    {
        name: 'lu',
        age: 27
    }
]

console.log(people.find((element, index, array) => element.age > 20)) // {name: 'fan',age: 29}
console.log(people.findIndex((element, index, array) => element.age > 20)) // 0

2.9归并方法

  • arr.reduce()
  • arr.reduceRight()

一个为从数组第一项开始遍历到最后一项,后者为从最后一项遍历到第一项。

都将接受两个参数,一个为对每一项运行的归并函数,一个为以之为归并起点的初始值。(不传第二个参数那么默认从第二个元素开始

传入的归并函数包括四个参数,分别为归并值、当前值、当前项的索引、数组本身。

let values = [1,2,3,4,5]
sum = values.reduce((prev, cur, index, array) => prev + cur)
console.log(sum) // 15

归并函数的返回值作为下一次调用归并函数的第一个参数即prev

3.字符串的常用方法有哪些?

对于增加字符串原内容:

除了常用+以及${}进行字符串拼接之外,还可通过concat用于将一个或多个字符串拼接成一个新字符串。

一般用slice(),对原数组进行裁剪。

trim()用于去除字符串中的空格,trimLeft()trimRight()用于去除前后的空格

toLowerCase()toUpperCase()实现大小写转化

split()按照传入的参数拆分数组中的每一项。

let str = "12+23+34"
let arr = str.split("+") // [12,23,34]

replace()接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素

4.谈谈 JavaScript 中的类型转换机制

7种数据类型在声明时并不会声明数据类型,只有在运行后才能知道(弱类型?),程序在运行时可能会触发类型转换,包括:强制转换和自动转换,即显示转换和隐式转换。

4.1显示转换

Number()强制转换为数组

image-20220221160521721

空字符串一般转换为0,只含数字的话就被转成数值,否则就是NaN

对象一般都转为NaN

只要有一个字符无法转成数值,整个字符串就会被转为NaN

parseInt()逐个解析字符,遇到无法转换的就会停下。

parseInt('32a3') //32

String()将任意类型转为字符串

image-20220221160935333

Boolean()可以将任意类型的值转为布尔值

  • undefined
  • null
  • false
  • +0
  • -0
  • NaN
  • ""

除了上面几种会被转化成false,其他都换被转化成true

4.2隐式转换

发生隐式转换的场景包括:

  • 比较运算(==!=><)、ifwhile需要布尔值的地方
  • 算术运算(+-*/%

遇到预期为字符串的地方,就会将非字符串的值自动转为字符串,常发生在+运算中,一旦存在字符串,则会进行字符串拼接操作。

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

除了+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值

5.== 和 ===区别,分别在什么情况使用

等于操作符==在比较中会进行隐式转换

如果任一操作数是布尔值,则将其转换为数值再比较是否相等

let result1 = (true == 1); // true

如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等

let result1 = ("55" == 55); // true

如果个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法取得其原始值,再根据前面的规则进行比较

let obj = {valueOf:function(){return 1}}
let result1 = (obj == 1); // true

nullundefined相等

let result1 = (null == undefined ); // true

如果有任一操作数是 NaN ,则相等操作符返回 false

let result1 = (NaN == NaN ); // false

如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true

let obj1 = {name:"xxx"}
let obj2 = {name:"xxx"}
let result1 = (obj1 == obj2 ); // false

下面进一步做个小结:

  • 两个都为简单类型,字符串和布尔值都会转换成数值,再比较
  • 简单类型与引用类型比较,对象转化成其原始类型的值,再比较
  • 两个都为引用类型,则比较它们是否指向同一个对象
  • nullundefined 相等
  • 存在NaN 则返回 false

全等操作符===,只有在两个操作数类型相同、值也相同时(即不发生转换)才返回true

let result1 = ("55" === 55); // false,不相等,因为数据类型不同
let result1 = ("55" == 55); // true,会自动发生隐式转换

总结:除了在比较对象属性为null或者undefined的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)

6.深拷贝浅拷贝的区别?如何实现一个深拷贝?

赋值、浅拷贝、深拷贝对原始数据的影响:

image-20220221171802414

赋值:当把一个对象赋值给一个新变量时,赋的是该对象在栈中的地址,而不是堆中的数据。新变量和旧对象共用一个内存地址,无论修改对象中的数据还是对象中子对象的数据对两者都有影响。

浅拷贝:基本类型时拷贝的就是基本类型的值;如果属性为引用类型,拷贝的就是内存地址,两个对象指向相同的地址,这和赋值相同,但是拷贝过程中只会修改子对象中的数据。

深拷贝:和浅拷贝同理,但是拷贝过程中对数据的修改不会影响原数据的变化,相当于要了他的壳子,新开了一个栈,两个对象指向不同的地址,新建自己的数据。

浅拷贝的实现方式:

1.Object.assign()传入两个参数,新对象和原对象

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
  1. Array.prototype.concat()
let arr = [1, 3, {
   username: 'kobe'
}];
let arr2=arr.concat(); 
  1. Array.prototype.slice()
let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();

4.拓展运算符

const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

5.Object.create

6.Array构造函数方法

let arr = [1, 3, {
   username: ' kobe'
}];
let arr4 = Array.from(arr)

深拷贝的实现方式:

  1. JSON.parse(JSON.stringify())
let arr = [1, 3, {
   username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));

JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

2.手写循环递归

function deepClone(obj) {
    // 判断要进行的深拷贝对象是对象还是数组
    let cloneObj = Array.isArray(obj) ? [] : {}
    if(obj && typeof obj === 'object') {
        for(key in obj) {
            // 判断一下key是不是obj自己拥有的属性,保证key不是原型属性
            if(obj.hasOwnProperty(key)) {
                if(obj[key] && typeof obj[key] === 'object') {
                    cloneObj[key] = deepClone(obj[key]);
                } else {
                    cloneObj[key] = obj[key];
                }
            }
        }
    }
    return cloneObj
}

7.说说你对闭包的理解?闭包使用场景

闭包让你可以在一个内层函数中访问到其外层函数的作用域。

使用闭包主要是为了设计私有的方法和变量,优点是可以避免全局变量的污染,缺点是占用内存。函数创建时闭包随之诞生。

特点:

  • 函数嵌套函数。
  • 在函数内部可以引用外部的参数和变量。
  • 参数和变量不会以垃圾回收机制回收,占用内存。

任何闭包的使用场景都离不开这两点:

  • 创建私有变量
  • 延长变量的生命周期

8.JavaScript原型,原型链 ? 有什么特点?为什么设计原型链?

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身

image-20220222164703213

原型链可以实现类的继承和属性的共享,子类的实例也可以访问到父类的属性和方法。、

实现类的继承,简化代码,实现复用。

9.谈谈this对象的理解

this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

  • 默认绑定
  • 隐式绑定
  • new绑定
  • 显示绑定

默认绑定就是基础定义。

隐式绑定即函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

new绑定即通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象。

显示绑定即apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数。

10.类的创建与继承

ES6创建类:

//类的创建、
class Parent {
    constructor(color, speed) {
        this.color = color
        this.speed = speed
        this.dirve = function() {
            console.log(wuwuwu)
        }
        //dirve() {
        // 	console.log(wuwuwu)
    	//}  //原型方法写法
    }
}
//继承
class Truck extends Parent {
    constructor(color, speed) {
        super(color, speed)
        this.container = true
    }
}

ES5创建类:

//类的创建
function Parent() {
    this.color = 'red'
    this.speed = '100'
    this.dirve = function() {
        console.log("wuwuwu")
    }
}
//在原型链上添加一个原型方法
Parent.prototype.fly = function() {
    console.log(wuhu)
}
const parent = new Parent()

继承方式:

  • 原型链继承
  • 构造函数继承(借助 call)
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承

1.原型链继承

function Child() { //创建一个空子类
}
Child.prototype = new Parent() //原型链继承
Child.prototype.name = 'aodi' //通过原型链添加属性
const child = new Child() //new一个子类实例对象

这样new出来的实例对象既是子类的实例,也是父类的实例。缺点是不能实现多继承。

2.构造函数继承

借助 call调用Parent函数

function Child() {
	Parent.call(this)
	this.name = 'aodi'
}
const child = new Child()
console.log(child.fly) //会报错,因为构造函数继承不能继承通过原型对象添加的属性和方法

可以实现多继承,但是不能继承通过原型对象添加的属性和方法

3.组合继承

function Child() {
	Parent.call(this)
	this.name = 'aodi'
}
Child.prototype = new Parent() //原型链继承
Child.prototype.constructor = Child //手动挂上构造器,指向自己的构造函数

const child = new Child()

解决了以上两个问题,但是调用了两次父类构造函数,性能变差。

4.原型式继承

借助Object.create方法实现实例对象的继承

const child = Object.create(parent)

Object.create为浅拷贝,多个实例对象的引用类型属性指向相同的内存地址,有可能被篡改。

5.寄生式继承

在原型式继承的基础上再添加一些方法,缺点同上。

6.寄生组合式继承

最优的方法?代码实现有问题,不需要匿名函数?

function Child() {
    Parent.call(this) //组合继承
    this.name = 'aodi'
}
(function() {
    //创建一个空对象
    const Super = function() {}
    //将实例对象作为子类的原型
    Super.prototype = Parent.prototype
    Child.prototype = new Super() //原型链继承
}) //寄生式继承
const child = new Child()

image-20220222220659007

11.说说JavaScript中的事件模型

事件可以理解为发生的一些交互操作,如加载、点击事件等。

事件发生的顺序可以引出事件流的概念,事件流都会经历三个阶段:

  • 事件捕获阶段(capture phase)
  • 处于目标阶段(target phase)
  • 事件冒泡阶段(bubbling phase)

image-20220222221339005

标准的事件模型即addEventListener来实现对DOM的操作,这个方法须传入三个参数,分别为:

  • eventType指定事件类型(不要加on)--如click
  • handler是事件处理函数--函数名
  • useCapture是一个boolean用于指定是否在捕获阶段进行处理,一般设置为false与IE浏览器保持一致,即冒泡阶段处理,点击最下层的DOM元素,从下向上挨个触发。
btn.addEventListener(‘click’, showMessage, false);

12.typeof 与 instanceof 区别

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

引用类型数据(Function/Array/Object),用typeof来判断的话,除了function会被识别出来之外,其余的都输出object

如果我们想要判断一个变量是否存在,可以使用typeof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数(引用类型)生成的对象

aodi instanceof Car

在判断是否为数组时,用instanceof判断可能存在问题,仅用于网页中只有一个全局执行上下文(会有两个版本的Array构造函数?),因此提出了:

Array.isArray()方法

单纯就是为了确定是否为数组,而不管是哪个全局上下文创建的。

总结:

typeof返回的是一个变量的基本类型,而instanceof返回的是一个布尔值,只能判断引用类型数据。

通用的检测数据类型方法:

Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"

13.说说new操作符具体干了什么?

  • 创建一个新的对象obj
  • 将对象与构建函数通过原型链连接起来
  • 将构建函数中的this绑定到新建的对象obj
  • 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

手撕new操作符:

function myNew(Func, ...args) {
    //1 创建一个新对象
    const obj = {}
    //2 新对象原型指向构造函数原型对象,把两者通过原型链连起来
    obj.__proto__ = Func.prototype
    //3 将构造函数的this绑定到新建对象上
    const result = Func.apply(obj, args)
    //4 根据构造函数返回类型判断
    return result instanceof Object ? result : obj
}

14.ajax原理是什么?如何实现?

Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面

image-20220223094515030

实现过程:

  • 创建 Ajax的核心对象 XMLHttpRequest对象
  • 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
  • 构建请求所需的数据内容,并通过XMLHttpRequest 对象的 send() 方法发送给服务器端
  • 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
  • 接受并处理服务端向客户端响应的数据结果
  • 将处理结果更新到 HTML页面中

手撕实现过程:

const request = new XMLHttpRequest()
request.onreadystatechange = function(e) {
    if (request.readystate === 4) { //整个请求过程完毕
        if (request.status >= 200 && request.status <= 300) {
            console.log(request.responseText) //服务端返回的结果
        } else if (request.status >= 400) {
            console.log("错误信息:"+ request.status)
        }
    }
}

15.改变函数内部 this 指针的指向函数(bind,apply,call ) 的区别

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window,只改变一次

call方法的第一个参数也是this的指向,后面传入的是一个 列表

apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

fn.call(obj,1,2); // this会变成传入的obj,传入的参数是一个参数列表;
fn(1,2) // this指向window,只改变一次

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // bindFn函数的this永远指向obj

总结:

applycall传入的第一个参数都是this要指向的那个对象,第二个参数apply是数组,call是一个个的参数,会立即执行且两者的都只改变this指向一次;bind会返回一个新的函数,这个函数不会立刻执行,需要传入参数调用且该函数永久改变this指向。

var obj = {
    z: 1
};
var obj1 = {
    z: 2
};

function fn(x, y) {
    console.log(x + y + this.z);
};
// call与apply
fn.call(obj, 2, 3); //6
fn.apply(obj, [2, 3]); //6

var bound = fn.bind(obj, 2);
bound(3); //6
//尝试修改bind返回函数的this
bound.call(obj1, 3); //6

16.DOM常见的操作有哪些?

创建节点:

createElement

创建新元素,接受一个参数,即要创建元素的标签名

const divEl = document.createElement("div");

createTextNode

创建一个文本节点

const textEl = document.createTextNode("content");

createDocumentFragment

用来创建一个文档碎片,它表示一种轻量级的文档,主要是用来存储临时节点,然后把文档碎片的内容一次性添加到DOM

const fragment = document.createDocumentFragment();

当请求把一个DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment自身,而是它的所有子孙节点

createAttribute

创建属性节点,可以是自定义属性

const dataAttribute = document.createAttribute('custom');
consle.log(dataAttribute);

获取节点:

querySelector

传入任何有效的css 选择器,即可选中单个 DOM元素(首个):

document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')

如果页面上没有指定的元素时,返回 null

querySelectorAll

返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表

const notLive = document.querySelectorAll("p");

更新节点:

innerHTML

不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树。

innerText、textContent

自动对字符串进行HTML编码,保证无法设置任何HTML标签

style

DOM节点的style属性对应所有的CSS,可以直接获取或设置。遇到-需要转化为驼峰命名

添加节点:

innerHTML

如果这个DOM节点是空的,例如,<div></div>,那么,直接使用innerHTML = '<span>child</span>'就可以修改DOM节点的内容,相当于添加了新的DOM节点

如果这个DOM节点不是空的,那就不能这么做,因为innerHTML会直接替换掉原来的所有子节点

appendChild

把一个子节点添加到父节点的最后一个子节点

删除节点

删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉

17.说说你对BOM的理解,常见的BOM对象你了解哪些?

BOM( Browser Object Model ),浏览器对象模型,提供一些独立于内容(浏览器的全部内容可以看成DOM)与浏览器进行交互的对象(BOM),其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退、前进、刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率。

DOM,文档对象模型,对文档中的内容进行操作。

BOM,浏览器对象模型,对浏览器本身进行操作。主要用于浏览器窗口之间的通讯、窗口的访问与交互。

image-20220227172558590

常见的BOM对象:(每个对象都有一系列对应的方法和属性)

  1. window(核心)表示整个浏览器窗口。

    • document 对象
    • history 对象,浏览器历史记录
    • location 对象,设置主机号、端口号等,加载文档等
    • navigator 对象
    • frames 对象
    • screen 对象,获取某些关于用户屏幕的信息

18.防抖和节流?应用场景?

两者本质是为了优化高频率执行代码的一种方式,

防抖throttle:n秒之后将执行某事件,若在n秒内被重复触发了将会重新计时,如电梯例子。

节流debounce:n秒内只运行一次,若在此期间被重复触发了,只有一次生效。

应用场景:

防抖在连续发生的事件中,只需要触发一次的场景有:

  • 搜索框的输入,只需要最后触发一次。
  • 在输入需要验证合理性的输入框中。
  • 调整窗口大小时,只需要最后计算调整完的大小,防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,如加载更多或滚到底部监听
  • 搜索框?搜索联想功能(提示词)

19.内存泄漏?几种内存泄露的情况?

内存泄漏是由于疏忽或者错误导致程序未能释放已经不再使用的内存,造成该段内存的浪费。

js具有自动垃圾回收机制,执行环境会对使用的内存进行管理,定期找出不再使用的变量,释放其内存。

一般有两种实现方式:

  • 标记清除
  • 引用计数

常见的内存泄露情况:

1. 意外的全局变量

function my() {
    name="my name is tokey"
    this.age = 12 // this指向了window
}
my()
console.log(window) // 会多出来两个全局变量 

未声明的变量会在全局最高对象上创建它的引用,浏览器上就添加到window,node环境就添加到globel

2. console.log也不会被垃圾回收机制回收,造成内存泄漏

3. 闭包

一个简单的闭包就是一个函数A返回一另个内联的函数B,即使函数A执行完了,函数B也能访问函数A中的变量。即私有变量?

function my(name) {
    function sendName() {
        console.log(name)
    }
    return sendName
}
var test=my("tokey")
test()  //tokey

my()内部创建的sendName()函数是不会被回收的,因为它被全局变量test引用,处于随时被调用的状态。

4. DOM泄露

const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用

对DOM元素的引用没有及时解除引用会造成内存泄露。

5. 定时器

setInterval()和链式setTimeout()在使用完之后如果没有手动关闭,会一直存在执行占用内存,所以在不用的时候我们可以通过clearInterval()clearTimeout()来关闭其对应的定时器,释放内存。

20.判断数组的方式?

1.通过instanceof判断

instanceof运算符会判断构造函数Array的prototype属性是否出现在实例对象a的原型链中。

let arr = []

console.log(arr instanceof Array )// true

// arr.__proto__ = Array.prototype

2.通过constructor判断

实例对象arr的构造函数属性constructor指向构造函数

let arr = []
console.log(arr.constructor === Array)
// arr.constructor = Array

3.通过Object.prototype.toString.call()判断

Object.prototype.toString.call()可以获取到实例对象的类型。

let arr = []
Object.prototype.toString.call(arr) // [object Array]
console.log(Object.prototype.toString.call(arr) === '[object Array]') //true

4.通过Array.isArray()判断

判断对象是否为数组

let arr = []
console.log(Array.isArray(arr)) // true

5.Array原型链上的isPrototypeOf

isPrototypeOf() 用于测试一个对象是否存在于构造函数的原型链上

let arr = [];
console.log(Array.prototype.isPrototypeOf(arr)); // true

6.Object.getPrototypeOf

let arr = [];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

21.获取数组的最大值

1.ES6扩展运算符

max()里的参数不能为数组,返回的是“一组数”中的最大值,所以需要通过扩展运算符对数组进行解构,变成参数列表

Math.max(...arr)

2.ES5 apply方法,和1同理

Math.max.apply(null, arr)

Math.max.apply(null)等同于Math.max(),这样apply是可以接收一个数组作为参数的

3.数组sort()方法

arr.sort((num1, num2) => {
	return num1 - num2 < 0 //由大到小排序
})
arr[0] //取第一个元素

4.reduce()方法

对数组中的每个元素执行传入的回调函数,把结果汇总为单个返回值

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

22.块级作用域

使下面输出为0,1,2,且每隔一秒输出一个,直接输出的话设置0即可

for(var i = 0; i < 3; i++) {
 setTimeout(function() {
   console.log(i)  // 333
 }, 1000 * i) 
}

ES6块级作用域

for(let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i) // 0,1,2
    }, 1000 * i)
}

ES5块级作用域

for(var i = 0; i < 3; i++) {
    (function(i) {
        setTimeout(function() {
            console.log(i)
        }, 1000*i)
    })(i)
}

TS相关

1.理解?和js的区别?

TS是JS的类型的超集,对JS进行了扩展。是一种提供类型注释、类型检查的语言,在编译阶段就可以检查出类型的错误。

区别的话,JS是脚本语言,所有语句都写在脚本标签里;TS是面向对象编程语言;

JS是弱类型的,TS是强类型的语言。

TS在编译时会转换为JS,所以更耗时。

2.TS的数据类型?

NNSBUATAE

基本类型(NNSBU)引用类型(Array、Function、Object)

  • boolean(布尔类型)
  • number(数字类型)
  • string(字符串类型)
  • null 和 undefined 类型
  • array(数组类型)
  • object 对象类型

原始类型:(TAEVN)

  • tuple(元组类型):表示一个已知元素数量和类型的数组,各元素的类型不必相同。
  • void 类型 :用于标识方法返回值的类型,表示该方法没有返回值。
  • never 类型 :一般用来指定那些总是会抛出异常、无限循环。
  • enum(枚举类型):使用枚举类型可以为一组数值赋予名字
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
  • any(任意类型)

算法:

位运算

最长递增子序列(vue的diff算法源码)(贪心、二分、动态规划)

链表