0
点赞
收藏
分享

微信扫一扫

JS基础面试总结

大师的学徒 2022-03-24 阅读 82
javascript

1、var、let和const区别?

var a.有变量的提升 b.没有块级作用域 c.可以重新赋值和更改
let a.有块级作用域,有变量死区
const a.有块级作用域,有变量死区 b.常量,不可更改

2、this的指向问题?

重点:箭头函数的this指向问题:是当前定义的这个函数;箭头函数中是没有自己的this的,指的是自己所处的上下文中的this;箭头函数不支持 arguments 对象

  1. 全局作用域下输出this 它是window对象
  2. 构造函数实例化出来的对象,this指向这个对象
  3. setTimeout的this指向window
  4. 函数内部的this指向window
    bind、apply、call都可以改变this的指向、
    bind会返回一个新的函数(节省内存)

3、数组和字符串常用方法?

3.1 数组:
改变原数组的方法?
var arr = []
arr.push(): 尾部新增
arr.pop():尾部删除(返回删除元素的值)
arr.unshift():首部新增
arr.shift():首部删除(返回数组长度)
arr.splice():替换或删除
arr.revere():反转
arr.fill():重新填值
arr.sort():排序(数字从小到大 ->字母排序)
arr.copyWithin():浅复制
不改变原数组的方法?
var arr = []
arr.slice() // 返回:start、end选定的元素
arr.map() // 返回:一个新数组
arr.forEach() // 返回:undefined
arr.every() // 返回:布尔值 举例:return item > 5;(一真则真)
arr.some() // 返回:布尔值 举例:return item > 5;(一假则假)
arr.filter() // 返回:一个新数组,是return值为true条件的集合
arr.entries() // 返回一个数组的迭代对象,该对象包含数组的键值对
arr.find() // 返回的就是找到的第一个元素的值,没有找到就返回-1;
arr.concat(‘1’,[‘2’,‘3’]) // 返回其他新拼接数组
arr.reduce()
reduce用法参考来源:https://www.cnblogs.com/smallpen/p/10249288.html
补充:清空数据?
一、arr = []
二、arr.length = 0在这里插入图片描述

三、arr.aplice(0, arr.length)
3.2 字符串:

4、两个不同的js文件调用问题?

es6模块机制参考来源:https://zhuanlan.zhihu.com/p/33843378

5、原型与原型链?

在这里插入图片描述

5、深拷贝与浅拷贝?

类型考虑/Symbol/Date等的构造/循环优化/共用引用优化等等

5、ES6新特性,常用API?

let、const/promise/箭头函数/上下文this的指向/class类/new

5、详解promise?

5.1 promise.resolve是干嘛的?
用于将现有对象转换为Promise对象,从而控制异步流程
5.2 promise.then如何实现链式调用?
then()方法会返回一个新的Promise实例,所以then()方法后面可以继续跟另一个then()方法进行链式调用
5.3 promise.then不返还一个promise还能用then吗?
可以
5.4 promise.finally的作用,如何自己实现finally?
用于指定不管 Promise 对象最后状态如何,都会执行的操作;是then方法的特例
5.5 promise原理?
promise 使用的是回调函数,只不过是把回调封装在了内部,使用方式是一直通过 then 方法进行链式调用。

6、数组引用的方式?

下标法/索引法/布尔法

7、如何获取请求url中的?后的参数?

步骤:
1.indexOf 2.substr 3.split 4.for循环

8、闭包问题及优化?

闭包就是函数外能够读取其他函数内部变量的函数;清空变量(null)

9、事件循环?

参考来源:https://www.jianshu.com/p/12b9f73c5a4f/

10、事件绑定方式?

一. 在DOM元素中直接绑定(写法:原生/自定义)
二. 在JavaScript代码中绑定
三. 绑定事件监听函数(addEventListener()/attachEvent() )

11、事件触发流程?

捕获、监听目标和冒泡
11.1 捕获阶段能终止吗?
可以,用stopPropagation()

12、如何终止冒泡?

标准的DOM方法
e.stopPropagation();
非标准的方式 老版本的IE支持
e.cancelBubble = true;

13、事件委托原理?

事件委托利用事件冒泡(从最深的节点开始,然后逐步向上传播事件)只在他们的父元素上指定一个事件处。
通俗的说
1 本来该给所有的子元素注册事件,现在只需要给父元素注册事件
2 动态创建的元素也具有该事件

$('#box').delegate('span', 'click', function () {
    // 此处的this,真正触发事件的元素
    console.log($(this).text());
  });

14、js异步方式?

callback/event/promise
setTimeout/await

15、如何实现one绑定事件?

one(type,[data],fn)

16、内存泄漏及解决?

说明:
不再用到的内存,没有及时释放。
JS垃圾回收机制?
标记清除(浏览器端常用),会标记该变量"进入环境",此时变量不能被清除,当离开环境时,变量被标记为"离开环境",垃圾回收可以回收变量占用的空
补充:
$(’#box’).empty();清空,元素本身还在
innerHTML = ‘’; 可能会有内存泄漏,无法清除元素的事件处理函数
$(’#box’).html(’’); 没有内存泄漏的问题
$(’#box’).remove(); 移除
js对象循环引用会导致什么问题?
对象不会被销毁,导致内存泄漏

17、数据类型判断?

一、typeof(适用于基础类型)
二、object.prototype.toString.call方法(最靠谱)
三、instanceof(判断 A 是否为 B 的实例,不常用)
四、toString(原型方式判断)
五、constructor 判断(实例通过constrcutor对象来访问它的构造函数)
六、isArray(数组专用方法判断)

18、new做了什么事情?

  1. 内存中创建了一个空对象
  2. 把this指向空对象
  3. 执行构造函数 (设置属性和方法,可以调用其它方法/函数)
  4. 最后返回刚刚创建的对象

19、Js与Jquery的相互转化?

待补充~~~

20、继承的几种方式?

先写一个父类Demo

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  this.age = age;
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

一、原型链继承
缺点
子类型无法给超类型传递参数;Child.prototype.say要写在Child.prototype = new Parent(‘father’)之后,不然会被覆盖掉。
特点:
1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2.父类新增原型方法/原型属性,子类都能访问到
3.简单,易于实现
缺点:
1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
2.无法实现多继承
3.来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例1)
4.创建子类实例时,无法向父类构造函数传参
Demo00

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

Demo01

function WangJ() {
    this.name = name;
    this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
    console.log('给公司YIYIYIYIYI投资manymanymany股票了')
}
function WangS(dog) {
    this.dog = dog;
}
    // 这句话的意思是,将王J的所有属性挂载到王S的原型链上
    WangS.prototype = new WangJ();
    //通过原型链继承创建出来的两个网红实例都可以继承不了王J原型链上的的tv属性和财富方法
    //但是原型的所有方法只能写一次,不能重写(实例指向的是改属性的引用)
WangS.prototype = new WangJ();
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying1'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')
console.log(WangHong1.color)//["red", "orange", "green", "blue"]
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票

var WangHong2 = new WangS('keke2');
WangHong2.name = 'doudeer2'
console.log(WangHong2.name)//undefined
console.log(WangHong2.color)//["red", "orange", "green", "blue"]
WangHong2.color.push('gray')
console.log(WangHong1.color)//["red", "orange", "green", "blue", "gray"]
console.log(WangHong2.color)//["red", "orange", "green", "blue", "gray"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票

二、构造函数继承(实例属性继承、伪造对象或经典继承)
缺点:
1.在构造函数中设置方法会影响性能,多个对象会存储多份function;
无法复用一些公用函数。
2.如果对象比较多,方法比较多,可能会出现命名冲突(函数封装方法)
3.不管有多少对象,原型中的方法在内存中只有一份(重点使用)
实例对象的原型p1.proto 是非标准的属性,开发中不要使用
4.实例并不是父类的实例,只是子类的实例
5.只能继承父类的实例属性和方法,不能继承原型属性/方法
6.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

特点:
1.解决了1中,子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类传递参数
3.可以实现多继承(call多个父类对象)
推荐指数:★★
Demo00

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true核心: 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Father(){
    this.color = ["red", "pink", "yellow"];
}
// 借助call继承
function Son(){
    Father.call(this);
    Father.apply(this);
}
var s1 = new Son();

Demo01

  function WangJ(name, age) {
        this.name = name;
        this.color = ['red','orange','green'];
    }
    WangJ.prototype.tv = '豆瓣儿'
    WangJ.prototype.money = function () {
        console.log('给公司YIYIYI投资股票')
    }
    function WangS( dog) {
        // 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
        WangJ.call(this)
        this.dog = dog;
    }
    // 通过构造函数继承创建出来的两个网红实例只能继承王J的基本属性
    // 通过构造函数继承创建出来的两个网红实例都不能继承不了王J原型链上的的tv属性和财富方法
    var WangHong1 = new WangS( 'keke1');
    WangHong1.name='xuey'
    WangHong1.color.push('blue')
    console.log(WangHong1.color)//["red", "orange", "green", "blue"]
    console.log(WangHong1.name)//xueying
    console.log(WangHong1.tv);//undefined
    console.log(WangHong1.dog);//keke1
    console.log(WangHong1.money)//undefined

    var WangHong2 = new WangS('keke2');
    WangHong2.name='doudeer'
    console.log(WangHong2.name)//xueying
    console.log(WangHong2.color)//["red", "orange", "green"]
    console.log(WangHong2.tv);//undefined
    console.log(WangHong2.dog);//keke1
    console.log(WangHong2.money)//undefined

三、组合继承(构造+原型)
优点:
弥补了原型和构造函数的缺点,使用原型链继承原型方法, 使用借用构造函数继承实例属性。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
总结:
1.弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
2.既是子类的实例,也是父类的实例
3.不存在引用属性共享问题
4.可传参
5.函数可复用

缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
推荐指数:★★★★(仅仅多消耗了一点内存)
Demo00

组合继承也是需要修复构造函数指向的
function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

Demo01

function WangJ() {
    this.name = name;
    this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
    console.log('给公司YIYIYI投资more股票')
}
function WangS(dog) {
    // 这句话的意思是,将J的所有属性挂载到王S的构造方法上
    WangJ.call(this)
    this.dog = dog;
}
// 这句话的意思是,将王J的所有成员挂载到王S的原型链上
WangS.prototype = new WangJ();
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
console.log(WangHong1.color)
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
//两个实例是独立的
var WangHong2 = new WangS('keke2');
console.log(WangHong2)//{name: "", color: Array(3), dog: "keke2"}
WangHong2.name = 'doudeer'
console.log(WangHong2.name)//undefined
WangHong2.color.push('black')
console.log(WangHong2.color)//["red", "orange", "green", "black"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
   
console.log(WangHong2)//WangS {name: "doudeer", color: Array(4), dog: "keke2"}

Demo01优化方式一

// 直接把父类的原型对象赋给子类的原型对象
  function WangJ() {
    this.name = name;
    this.color = ['red','orange','green'];
}
WangJ.prototype.tv = '豆瓣儿'
WangJ.prototype.money = function () {
    console.log('给公司YIYIYI投资more股票')
}
function WangS(dog) {
    // 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
    WangJ.call(this)
    this.dog = dog;
}
// 这句话的意思是,将王J的所有成员挂载到王S的原型链上
WangS.prototype = WangJ.prototype;
var WangHong1 = new WangS('keke1');
WangHong1.name = 'xueying'
console.log(WangHong1.name)//undefined
WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
console.log(WangHong1.color)
console.log(WangHong1.tv);//male
console.log(WangHong1.dog);//keke1
console.log(WangHong1.money())//给公司YIYIYI投资股票
//两个实例是独立的
var WangHong2 = new WangS('keke2');
WangHong2.name = 'doudeer'
console.log(WangHong2.name)//undefined
WangHong2.color.push('black')
console.log(WangHong2.color)//["red", "orange", "green", "black"]
console.log(WangHong2.tv);//male
console.log(WangHong2.dog);//keke1
console.log(WangHong2.money())//给公司YIYIYI投资股票
// 缺点:父类的构造函数被执行了两次
// 第一次:WangS.prototype = new WangJ();
// 第二次:实例化的时候( WangHong1、 WangHong2)

Demo01优化方式二

<!-- 堪称继承最完美的方式!!! -->
    function WangJ() {
        this.name = name;
        this.color = ['red', 'orange', 'green'];
    }
    WangJ.prototype.tv = '豆瓣儿'
    WangJ.prototype.money = function () {
        console.log('给公司YIYIYI投资more股票')
    }
    function WangS(dog) {
        // 这句话的意思是,将王J的所有属性挂载到王S的构造方法上
        WangJ.call(this)
        this.dog = dog;
    }
    // 这句话的意思是,将王J的所有成员挂载到王S的原型链上
    WangS.prototype = Object.create(WangJ.prototype)
    WangS.prototype.constructor = WangS
    var WangHong1 = new WangS('keke1');
    WangHong1.name = 'xueying'
    console.log(WangHong1.name)//undefined
    WangHong1.color.push('blue')//"red", "orange", "green", "blue"]
    console.log(WangHong1.color)
    console.log(WangHong1.tv);//male
    console.log(WangHong1.dog);//keke1
    console.log(WangHong1.money())//给公司YIYIYI投资股票
    //两个实例是独立的
    var WangHong2 = new WangS('keke2');
    WangHong2.name = 'doudeer'
    console.log(WangHong2.name)//undefined
    WangHong2.color.push('black')
    console.log(WangHong2.color)//["red", "orange", "green", "black"]
    console.log(WangHong2.tv);//male
    console.log(WangHong2.dog);//keke1
    console.log(WangHong2.money())//给公司YIYIYI投资股票
    // 缺点:父类的构造函数被执行了两次
    // 第一次:WangS.prototype = new WangJ();
    // 第二次:实例化的时候( WangHong1、 WangHong2)

四、copy继承(深、浅)
深copy(所有成员(属性、方法))、浅copy(属性)
深拷贝,重新开辟一块内存空间,如果对象的成员是复杂类型,会把复杂类型的对象也拷贝一份
特点:
支持多继承
缺点:
1.效率较低,内存占用高(因为要拷贝父类的属性)
2.无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
推荐指数:★(缺点1)
Demo

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

Demo01(浅拷贝)

  // 浅拷贝(只会把父级最外层的成员继承过来,而子级与父级内层的对象会指向同一个引用)
    // 专业滴说,如果对象的成员是复杂类型,不会把复杂类型的对象拷贝一份,会公用父级的复杂对象
    function shadowCopy(parent, child) {
      for (var key in parent) {
        if (!child[key]) {
          child[key] = parent[key];
        }
      }
    }

    var wj= {
      name: '王J',
      money: 100000,
      cars: ['五菱宏光', '玛莎拉蒂'],
      dog: {
        name: 'BYD',
        age: 2
      }
    }

    var ws= {
      name: '王S'
    }

    shadowCopy(wj, ws);

    ws.dog.name = 'wkk';
    ws.money = 1;
    console.log(ws);
    console.log(wj);

Demo02(深拷贝)


// 深拷贝,如果对象的成员是复杂类型,会把复杂类型的对象也拷贝一份

function deepCopy(parent, child) {
  for (var key in parent) {
    // 判断child中是否有对应的属性
    if (child[key]) {
      continue;
    }
    // 判断parent[key] 对象的类型 是数组还是对象
    // instanceof
    // 先判断数组  ,因为数组也是对象
    if (parent[key] instanceof Function) {
      child[key] = parent[key];
    } else if (parent[key] instanceof Array) {
      child[key] = [];
      // 把parent[key] 数组 中所有的成员拷贝给child[key]
      deepCopy(parent[key], child[key]);
    } else if (parent[key] instanceof Object) {
      child[key] = {};
      // 把parent[key] 对象 中所有的成员拷贝给child[key]
      deepCopy(parent[key], child[key]);
    } else {
      // 拷贝基本类型的属性
      child[key] = parent[key];
    }
  }
}

var wj = {
  name: '王J',
  money: 100000,
  cars: ['五菱宏光', '玛莎拉蒂'],
  dog: {
    name: 'BYD',
    age: 2
  },
  play: function () {
    console.log('打高尔夫');
  }
}

var ws = {
  name: '王S'
}

deepCopy(wj, ws);

ws.dog.name = 'wangkeke';

console.log(ws);
console.log(wj);
function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

五、ES6继承extends
介绍
1
extends
Demo01
   

class WangJanLin {

};

class WangS extends WangJanLin {
    //这句话的意思是,调用父类的constructor
    // ES5在这写,必须定义constructor
    constructor(x, y, colors) {
        super(x, y)
        this.colors = colors
    }
    // 给子类定义一个方法
    toString() {
        return this.colors + '' + super.toString();//调用父类发tostring
    }
}
var WangHong1 = new WangS('颜色1', '颜色2', ['red', 'orange', 'green'])
console.log(WangHong1.colors)
console.log(WangHong1.toString())

var WangHong2 = new WangS('', '', ['red', 'orange', 'green', 'blue'])
console.log(WangHong2.colors)



function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类(新类/第三方类)
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

Cat.prototype.constructor = Cat; // 需要修复下构造函数

Demo寄生组合式
优点:解决了两次调用的问题

// 创建只继承原型对象的函数
    function inheritPrototype(parent, child) {
       // 创建一个原型对象副本-创建对象
       var prototype = new Object(parent.prototype);
       // 设置constructor属性-增强对象
       prototype.constructor = child;
 	   //指定对象
       child.prototype = prototype;
   }

     // 父亲类
     function Parent() {
         this.color = ['pink', 'red'];
    }
     Parent.prototype.sayHi = function() {
        console.log('Hi');
    }

    // 儿子类
     function Child() {
         Parent.call(this);
     }
 
    inheritPrototype(Parent, Child);

七、原型式继承

思想:基于已有的对象创建对象
Demo

function createAnother(o) {
     // 创建一个临时构造函数
     function F() {

    }
     // 将传入的对象作为它的原型
    F.prototype = o;
     // 返回一个实例
       return new F();
   }

附录Demo00

function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  //实例引用属性
  this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();

var tom = new Cat('Tom');
var kissy = new Cat('Kissy');

console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []

tom.name = 'Tom-New Name';
tom.features.push('eat');

//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']
原因分析:
关键点:属性查找过程
执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。

21、其他

举报

相关推荐

0 条评论