JS总结

177 阅读12分钟

1. JS有几种数据类型,其中基本数据类型有哪些!

(ES6之前)其中5种为基本类型:string,number,boolean,null,undefined,ES6出来的Symbol也是原始数据类型 ,表示独一无二的值。Object 为引用类型(范围挺大),也包括数组、函数ES2020(即 ES11),又增加了新类型:BigInt

2. 基本数据类型和引用数据类型有什么区别?

(1)两者作为函数的参数进行传递时:

        基本数据类型传入的是数据的副本,原数据的更改不会影响传入后的数据。         引用数据类型传入的是数据的引用地址,原数据的更改会影响传入后的数据。

(2)两者在内存中的存储位置:

        基本数据类型存储在栈中

        引用数据类型在栈中存储了指针,该指针指向的数据实体存储在堆中|

3 .判断数据类型的方法有哪些?

typeof 可以判断哪些类型?instanceof 做了什么?null为什么被typeof错误的判断为了'object'

判断数据类型的方法有哪些?

(1)利用typeof可以判断数据的类型;

   (2)A instanceof B可以用来判断A是否为B的实例,但它不能检测 null 和 undefined

比如:

  let animal = function(){};
  let dog = new animal();
  console.log(dog instanceof animal);//true

   (3)B.constructor == A可以判断A是否为B的原型,但constructor检测 Object与instanceof不一样,还可以处理基本数据类型的检测。

细节问题: 1.null和undefined是无效的对象,因此是不会有constructor存在的,这两种类型的数据可以通过第四种方法来判断。 2.JS对象的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor会丢失,constructor会默认为Object

(4) Object.prototype.toString(这个是判断类型最准的方法)

toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型

image.png

3. var、 let、const的区别是什么?

image.png

4. this指向的各种情况都有什么?

this的指向只有在调用时才能被确定,因为this是执行上下文的一部分。

(1) 全局作用域中的函数:其内部this指向window:

var a = 1;
function fn(){
console.log(this.a)
}
fn() //输出1

(2) 对象内部的函数:其内部this指向对象本身:

var a = 1;
var obj = {
  a:2,
  fn:function(){
  	console.log(this.a)
	}
}

obj.fn() //输出2

(3) 构造函数:其内部this指向生成的实例:

function createP(name,age){
	this.name = name //this.name指向P
  this.age = age //this.age指向P
}
var p = new createP("老李",46)

(4)由apply、call、bind改造的函数:其this指向第一个参数:

function add(c,d){
	return this.a + this.b + c + d
}
var o = {a:1,b:2)
add.call(o,5,7) //输出15

(5). 箭头函数:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。

5. (call()和apply()、bind)区别

(call()和apply()、bind)区别

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)  可以使用apply、call、bind方法改变this指向(并不会改变函数的作用域)。比较如下:

  (1)三者第一个参数都是this要指向的对象,也就是想指定的上下文,上下文就是指调用函数的那个对象(没有就指向全局window);

 (2)bind和call的第二个参数都是数组,apply接收多个参数并用逗号隔开;

  (3)apply和call只对原函数做改动,bind会返回新的函数(要生效还得再调用一次)。

6. 什么是闭包?

  闭包就是引用了其他函数作用域中变量的函数,这种模式通常在函数嵌套结构中实现。里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。闭包有如下作用

(1)加强封装,模拟实现私有变量;

(2)实现常驻内存的变量。

闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。

7.什么是原型、原型链

什么是原型、原型链

原型: JS声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型构造函数默认有一个prototype属性prototype的值指向函数的原型。同时原型中也有一个constructor属性,constructor的值指向原函数

   通过构造函数实例化出来的对象,并不具有prototype属性,其默认有一个__proto__属性,__proto__的值指向构造函数的原型对象。在原型对象上添加或修改的属性,在所有实例化出的对象上都可共享。

例如:函数Person有一个属性prototype,指向一个对象,当使用new操作符时,会把Person.prototype(原型对象)赋值给实例的__proto__(原型实例)属性。

原型链: 当在实例化的对象中访问一个属性时,首先会在该对象内部寻找,如找不到,则会向其__proto__指向的原型中寻找,如仍找不到,则继续向原型中__proto__指向的上级原型中寻找,直至找到或Object.prototype为止,这种链状过程即为原型链。

image.png

8. 继承方法

继承方法 实现继承的方法有哪些??

ES5与ES6继承的区别

(1)class+extends继承(ES6)

//类模板
class Animal {
  constructor(name){
    this.name = name
  }
}
//继承类
class Cat extends Animal{//重点。extends方法,内部用constructor+super
  constructor(name) {
    super(name);
    //super关键字指代父类的实例,即父类的this对象。
    //在子类构造函数中,调用super后,才可使用this关键字,否则报错。
  } //constructor可省略
  eat(){
    console.log("eating")
  }
}

描述:通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。(实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。)

(2)组合继承(ES5函数继承)

function Animal(color){//构造函数
     this.color = color
 }
 Animal.prototype.move = function(){} // 动物可以动
 function Dog(color, name){
     Animal.apply(this, arguments)//子集去继承父级
     this.name = name
 }
 // 下面三行实现 Dog.prototype.__proto__ = Animal.prototype
 function temp(){}
 temp.prototype = Animal.prototype//原型继承
 Dog.prototype = new temp()

 Dog.prototype.constuctor = Dog 
 Dog.prototype.say = function(){ console.log('汪')}

 var dog = new Dog('黄色','阿黄')
 console.log(dog.name)
 console.log(dog)

image.png

ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上

9. 浅拷贝与深拷贝有何区别?如何实现?

浅拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。

深拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。

image.png

image.png

  • 实现浅拷贝:

(1)、ES6:object.assign(),一层是深拷贝,多层是浅拷贝

//一层
let a = {
   username: 'kobe'
};
let b = Object.assign({},a);
b.username = 'wade';
console.log(b);//{username: "kobe"} 原对象没有被改变,是深拷贝
//多层
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade 原对象也改变了,是浅拷贝

(2)、展开运算符……

var a = { name : “hello” };
var b = { …a}; => 扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值
b.name = “hi”;
console.log(a);

(3)、自己封装函数实现for in

var a = { name : “hello” };
var b = copy(a);
b.name = ‘hi’;
console.log(a);
function copy(obj){
var result = { };
for(var attr in obj ){
result [attr] = obj[attr];
}
return result;
}
  • 实现深拷贝:

(1).JSON.parse()JSON.Stringify()

var a = {name = {age:20}};
var b = JSON.parse(JSON.Stringify(a));
b.name.age =30;
console.log(a);

弊端:

  1. 性能问题,stringify再解析其实需要耗费较多时间,特别是数据量大的时候。

2.它能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。 一些类型无法拷贝,例如函数(不输出),正则(输出空对象),时间对象(输出时间字符串),Undefiend(不输出)等等问题

(2).jQuery的$.extend()

调用$.extend(true, {}, ...)就可以实现深复制

var x= {a:1,b:{f:{g:1}},c:[1,2,3]};
var y=$.extend({},x)  浅拷贝
shallowcopy z=$.extend(true,{},x);深拷贝
y.b.f===x.b.f//true  
z.b.f===x.b.f//false  

弊端:

var objA = {};
var objB = {};
objA.b = objB;
objB.a = objA;
$.extend(true,{},a);//这个时候就出现异常了//Uncaught RangeError: Maximum call stack size exceeded(…)

也就是说,jQuery中的$.extend()并没有处理循环引用的问题。

(3).已封装函数与实现for in +递归

function deepClone(o) {
    // 判断如果不是引用类型,直接返回数据即可
    if (typeof o === 'string' || typeof o === 'number' || typeof o === 'boolean' || typeof o === 'undefined') {
        return o
    } else if (Array.isArray(o)) { // 如果是数组,则定义一个新数组,完成复制后返回
        // 注意,这里判断数组不能用typeof,因为typeof Array 返回的是object
        console.log(typeof [])  // --> object
        var _arr = []
        o.forEach(item => { _arr.push(item) })
        return _arr
    } else if (typeof o === 'object') {
        var _o = {}
        for (let key in o) {
            _o[key] = deepClone(o[key])
        }
        return _o
    }
}

var arr = [1, 2, 3, 5]
var cloneArr = deepClone(arr)
console.log(cloneArr)   // --> [ 1, 2, 3, 5 ]
console.log(arr === cloneArr)   // --> false

var obj = { name: 'ccc', age: 18 }
var cloneObj = deepClone(obj)
console.log(cloneObj)   // --> { name: 'ccc', age: 18 }
console.log(obj === cloneObj)   // false
obj.name = 'www'
console.log(obj)    // --> { name: 'www', age: 18 }
console.log(cloneObj)   // --> { name: 'ccc', age: 18 }

10.JavaScript数组去重

JavaScript数组去重(12种方法,史上最全)

解锁多种JavaScript数组去重姿势 一、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

二、利用sort()

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}
     var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined]      //NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对

三、利用filter

function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

四、利用利用Map数据结构去重

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。

function arrayNonRepeatfy(arr) {
  let map = new Map();
  let array = new Array();  // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) {  // 如果有该key值
      map .set(arr[i], true); 
    } else { 
      map .set(arr[i], false);   // 如果没有该key值
      array .push(arr[i]);
    }
  } 
  return array ;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

五:Array.prototype.reduce()

Array.prototype.unique = function () {
  return this.sort().reduce((init, current) => {
    if(init.length === 0 || init[init.length - 1] !== current){
      init.push(current);
    }
    return init;
  }, []);
}

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

image.png

(1).在箭头函数中,this属于词法作用域,直接由上下文确定,对于普通函数中指向不定的this,箭头函数中处理this无疑更加简单,如下:

//ES5普通函数
function Man(){
  this.age=22;
  return function(){
    this.age+1;
  }
}
var cala=new Man();
console.log(cala())//undefined

//ES6箭头函数
function Man(){
  this.age=22;
  return () => this.age+1;
}
var cala=new Man();
console.log(cala())//23

(2).箭头函数中没有arguments(我们可以用rest参数替代),也没有原型,也不能使用new 关键字,例如:

//没有arguments
var foo=(a,b)=>{return arguments[0]*arguments[1]}
console.log(foo(3,5))
//arguments is not defined

//没有原型
var Obj = () => {};
console.log(Obj.prototype); 
// undefined

//不能使用new 关键字
var Obj = () => {"hello world"};
var o = new Obj(); 
// TypeError: Obj is not a constructor

(3).箭头函数给数组排序

const arr = [10, 50, 30, 40, 20]
const s = arr.sort((a, b) => a - b)
console.log(s) // [10,20,30,40,50]

12. 数组操作

还在傻傻分不清ES5、Es6数组方法?各大姿势来袭

image.png

image.png

13. 如何遍历一个对象

image.png

1.for … in 循环遍历对象自身的和继承的可枚举属性(循环遍历对象自身的和继承的可枚举属性(不含Symbol属性).).

image.png

2、使用Object.keys() 遍历 (返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性).).

image.png

14. 如何遍历一个数组

JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in VS for/of

1、使用forEach遍历,以同时访问数组的下标与元素值:

image.png

2、使用for..in..遍历们,可以访问数组的下标,而不是实际的数组元素值

for (let i = 0; i < arr.length; ++i) {
    console.log(arr[i]);
}

for (let i in arr) {
    console.log(arr[i]);
}


3、使用for/of,则可以直接访问数组的元素值:

for (const v of arr) {
    console.log(v);
}

15. DOM和BOM的差异

JS DOM和BOM的差异

DOM:Document Object Model(文档对象模型)

其实光说文档对象模型还是很抽象的一个概念,到底什么是文档对象模型呢,其实说白了DOM就是针对HTML和XML提供的一个API(通俗地讲就是操作HTML的内容) BOM:Browser Obeject Model(浏览器对象模型)

前面说了DOM,那么BOM顾名思义就是为了控制浏览器行为的接口,比如跳转页面,获取屏幕大小等等

16. JS是如何实现异步的?

 JS引擎是单线程的,但又能实现异步的原因在于事件循环和任务队列体系。

    事件循环:

   JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。

    任务队列:

   异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如onclick, setTimeout, ajax处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,浏览器内核包含3种 webAPI,分别是 DOM Binding、network、timer模块。

onclick DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。

setTimeout 由 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。

ajax 由network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。

    主线程:

   JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。

   只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。

17. 什么是AJAX?如何实现?

ajax是一种能够实现局部网页刷新的技术,可以使网页异步刷新。

ajax的实现主要包括四个步骤:

(1)创建核心对象XMLhttpRequest;

(2)利用open方法打开与服务器的连接;

(3)利用send方法发送请求;("POST"请求时,还需额外设置请求头)

(4)监听服务器响应,接收返回值。

 var request = new XMLHttpRequest()
 request.open('GET', '/a/b/c?name=ff', true);
 request.onreadystatechange = function () {
   if(request.readyState === 4 && request.status === 200) {
     console.log(request.responseText);
   }};
 request.send();

18. 怎么理解宏任务,微任务???

   宏任务有:script(整体代码)、setTimeout、setInterval、I/O、页面渲染;

   微任务有:Promise.then、Object.observe、MutationObserver。

   执行顺序大致如下:

 主线程任务——>宏任务——>微任务——>微任务里的宏任务——>.......——>直到任务全部完成

19. EventLoop事件循环是什么?

  js是一门单线程的需要,它的异步操作都是通过事件循环来完成的。整个事件循环大体由执行栈、消息队列和微任务队列三个部分组成。

  同步代码会直接在执行栈中调用执行。

 定时器中的回调会在执行栈被清空且定时达成时推入执行栈中执行。

    promise、async异步函数的回调会被推入到微任务队列中,当执行栈被清空且异步操作完成时立即执行。

20. DOM事件模型和事件流?

DOM事件模型 包括事件捕获(自上而下触发)与事件冒泡(自下而上触发,ie用的就是冒泡)机制。基于事件冒泡机制可以完成事件代理。

DOM事件流 包括三个阶段事件捕获阶段、处于目标阶段、事件冒泡阶段。

21. require/import之间的区别?

(1)require是CommonJS语法,import是ES6语法;

(2)require只在后端服务器支持,import在高版本浏览器及Node中都可以支持;

(3)require引入的是原始导出值的复制,import则是导出值的引用;

(4)require时运行时动态加载,import是静态编译;

(5)require调用时默认不是严格模式,import则默认调用严格模式.

22、symbol数据类型

JavaScript中一种全新的数据类型-symbol

image.png

23. 26、一个页面接受两个请求,如何接受两个请求后做处理

Promise.All()

24.ts用了哪些关键字,定义类型

image.png

25.数组api,reduce怎么用(去重)

數組reduce链接 image.png

image.png

image.png

image.png

26. 数组api,Array.from怎么用(伪数组变成数组)

image.png

image.png

image.png

image.png

27. 一个数组如何转换为一个类数组对象呢?

目标数组只是为了得到对象的key的集合或者value的集合

var arr = []
for (let i in obj) {
    arr.push(i);        //key
    //arr.push(obj[i]); //值
}
console.log(arr);

for (let i in obj){},这个方法主要是用来遍历对象的,in后面是对象,i为key。

  var arr = []
for (let i in obj) {
  let o = {};
  o[i] = obj[i];
  arr.push(o)
}
console.log(arr);

27. forEach()、Map()、every()、some()、filter()

image.png

image.png

image.png

image.png

image.png

image.png

28、怎么删除数组某一个下标元素,splice

image.png

image.png

image.png

image.png

image.png

29. ES6基本语法

ES6基本语法

JavaScript、ES5和ES6的介绍和区别

ES6 入门教程

ES 6 新特性列表 赋值解构:

let singer = { first: "Bob", last: "Dylan" };
let { first: f, last: l } = singer; // 相当于 f = "Bob", l = "Dylan"
let [all, year, month, day] =  /^(dddd)-(dd)-(dd)$/.exec("2015-10-25");
let [x, y] = [1, 2, 3]; // x = 1, y = 2

29、Object.assign()

【ES6学习笔记之】Object.assign()

未完待续.......................