JS常见面试知识

值和引用

值在栈中保存 上->下
引用在堆中保存 下->上

深拷贝

问题:一个对象复制为另一个对象的时候,由于存放在堆区(具体再看),因此改变复制对象后原对象也会发生变换,因此需要深拷贝。
思路:
  1.首先判断该变量类型是否为对象,不是则直接return
  2.然后判断该变量类型为数组还是对象,创建相应的空的变量。
  3.循环遍历一下对象的内容,如果都是他的本身属性(非原型属性),那么就递归调用该函数实现深拷贝。由于到末端的值都不为对象,因此在递归的时候会return出去然后赋值给上级的key,完成深拷贝。

ES6新增浅拷贝语法糖Object.assign(target,...sources)

const obj1={
  name:'张三',
  age:18,
  address:{
    city:'上海'
  },
  arr:['a','b','c']
}

const obj2=deepClone(obj1)
obj2.address.city="北京"
console.log('obj1是'+obj1.address.city, '')
console.log('obj2是'+obj2.address.city, '')

function deepClone(obj) {
  if(typeof obj !=='object'||obj==null){
    return obj
  }
  let res 
  // 判断是否为数组
  if(obj instanceof Array){
    res=[]
  }else{
    res ={}
  }
  for(let key in obj){
    // 保证key不是原型的属性
    if(obj.hasOwnProperty(key)){
      // 递归调用 防止深层次的东西没有复制过来
      res[key]=deepClone(obj[key])
    }
  }
  return res
}

另一种写法

const obj1={
  name:'张三',
  age:18,
  address:{
    city:'上海'
  },
  arr:['a','b','c']
}

function deepCopy(newObj,oldObj){
  // 循环遍历老对象赋值给新对象
  for(var k in oldObj){
    // 获取老对象的值
    var item=oldObj[k]
    if(item instanceof Array){
      newObj[k]=[];
      return deepCopy(newObj[k],item)
    }else if(item instanceof Object){
      newObj[k]={};
      return deepCopy(newObj[k],item)
    }else{
      newObj[k]=item
    }
  }
}
var o={}
deepCopy(o,obj1)
// console.log(o);

字符串拼接

问题

  1. typeof能判断哪些类型
    值类型 函数 引用
  2. 何时使用 === 以及 ==
    除了null之外
  3. 值类型与引用类型的区别
  4. 深拷贝

原型

每个构造函数都有一个原型对象,来存放所有对象所共享方法
一般的,公共属性会用构造函数定义,而公共方法却用函数的原型对象存放。

    function person(name,age){
      this.name=name;
      this.age=age;
      // this.sing=function() {
      //   console.log(name+'我会唱歌');
      // }
    }
    // 原型对象
    person.prototype.sing=(name)=>console.log(name+'我会唱歌');
    var zys=new person('张三','18');
    console.log(zys);
    zys.sing(zys.name);

继承

  • extend 继承父类
  • super 执行父类构造
  • instanceof为类型判断
    在这里插入图片描述 每个构造函数/class(ES5没有类的概念)都有显式原型 prototype
    每个实例都有隐式原型__ proto __,指向prototype
    隐式原型指向显式原型,隐式找不到就去显式去找
    (原型存放方法)
    __ proto __与prototype都有一个constructor属性,指回构造函数本身。

原型对象的this指向:无论是对象还是构造函数的this都指向被调用的实例对象。

// 利用原型对象扩展内置对象方法
Array.prototype.sum=function(){
      var sum=0;
      for(var i=0;i<this.length;i++){
        sum+=this[i];
      }
      return sum;
    }
    var arr=[1,2,3];
    console.log(arr.sum(), '求和');
    console.log(Array.prototype)

原型链

构造函数.prototype===obj.__ proto __

父.prototype===子.prototype.__ proto __

在这里插入图片描述
hasOwnProperty() //验证是否为自身属性
在这里插入图片描述
问题

  1. 判断一个变量是否为数组
    instanceof Array
  2. 手写简易的jquery
    没搞懂
class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}

// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {

    }
    style(data) {
        
    }
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

3.class原型本质

作用域

  • 函数内部可以使用全局变量
  • 函数外部不能使用局部变量
  • 执行完毕后变量被销毁

闭包

闭包是指有权访问另一个函数作用域中的变量的函数
被访问的变量所在函数就是闭包函数。
作为参数被传入以及作为返回值返回,返回值返回可以通过闭包在全局调用局部变量,延伸了变量的作用范围。
案例1.

    // 点击li输出li的索引
    // 1.利用动态添加属性的方式
    var lis=document.querySelector('.nav').querySelectorAll('li')
    for(var i=0;i<lis.length;i++){
      lis[i].index=i;
      lis[i].onclick=function(){
        console.log(this.index);
      }
    }
    // 2.利用闭包的方式 不点的话就不会销毁,容易内存泄漏
    for(var i=0;i<lis.length;i++){
      (function(i) {
        // console.log(i);
        lis[i].onclick=function(){
          console.log(i);
        }
      })(i);
    }

案例2.

	var lis=document.querySelector('.nav').querySelectorAll('li')
    // 打印所有li内容
    for(var i=0;i<lis.length;i++){
      (function(i){
        setTimeout(() => {
          console.log(lis[i].innerHTML);
        }, 2000);
      })(i);
    }

自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方。

this

取值是在执行时确认,而不是定义
应用场景:

  • 普通函数调用
  • .call apply bind
    • call()、apply()、bind() 都是用来重定义 this 这个对象的,可以改变this的指向
    • 区别:
          1. call的参数后边挨着放。call(obj,params1,params2)。
          用途:可以实现继承。
          2. apply的参数第二个在数组中。call(obj,[‘params1’,‘params2’])。
          用途:将传递的参数数组转化为相应类型,可对数组进行操作。求最大值。
          3. bind的参数同第一个但是后边得加()到新的函数。call(obj,params1,params2)()
          用途:不立即调用。禁用按钮,三秒后开启。
// call实现继承属性
function Father(name,age){
      this.name=name;
      this.age=age;
    }
    function Son(name,age,score) {
      Father.call(this,name,age);
      this.score=score;
    }
    var son=new Son('张三',18)
    console.log(son)
// call实现继承方法
function Father(name,age){
      this.name=name;
      this.age=age;
    }
    Father.prototype.money=function(){
      console.log(100000);
    }
    function Son(name,age,score) {
      Father.call(this,name,age);
      this.score=score;
    }
    // Son.prototype=Father.prototype //不能这么做,会把子方法赋给父方法
    Son.prototype=new Father();
    // 利用对象的形式修改了原型对象,别忘了利用constructor来指向原来了构造函数
    Son.prototype.constructor=Son;
    Son.prototype.exam=function(){
      console.log(100);
    }
    var son=new Son('张三',18)
    console.log(son)
// apply求最大值
var arr=[1,2,4,5,1,2,3,6];
var max=Math.max.apply(Math,arr);
console.log(max);
// bind禁用按钮
var btn=document.querySelector('button');
btn.onclick=function(){
	this.disabled=true;
	var that=this;
	setTimeout(function(){
		// 定时器的this指向window
		// this.disabled=false;
		// 此时this是bind绑定的btn对象
		this.disabled=false;
		// 可以这么做
		// that.disabled=false;
	// 这么做最好
	}.bind(this),3000)
}

JavaScript 中 call()、apply()、bind() 的用法

  • .对象方法调用
  • class的方法中调用
  • 箭头函数(指向上一级的this)

问题

  1. this在不同场景下如何取值
  2. 手写bind函数

函数内的this指向

调用方式this指向
普通函数调用window
构造函数调用实例对象,原型对象方法也指向实例对象
对象方法调用方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即执行函数window
// 模拟 bind
Function.prototype.bind1 = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)

    // 获取 this(数组第一项)
    const t = args.shift()

    // fn1.bind(...) 中的 fn1
    const self = this

    // 返回一个函数
    return function () {
        return self.apply(t, args)
    }
}
  1. 闭包应用场景
      隐藏数据,只提供API。比如一个类的数据不能被外界修改
  2. 作用域和自由变量
  3. 闭包
  4. this

继承

原型链继承

  • 让新实例的原型等于父类的实例。

构造函数继承

  • 用.call()和.apply()将父类构造函数引入子类函数

组合继承(组合原型链继承和借用构造函数继承)(常用)
寄生式继承
寄生组合式继承
class继承-extends

引用链接

引用链接

单线程

js是单线程语言
js与DOM渲染之能用一个线程

异步

不会阻塞代码执行
使用场景

  • 网络请求
  • .定时任务

回调地狱callback hell
ajax异步调用接口不会顺序执行

 $.ajax({
      url:'http://localhost:3000/data',
      success:function(data){
        console.log(data)
      }
    })
    $.ajax({
      url:'http://localhost:3000/data1',
      success:function(data){
        console.log(data)
      }
    })
    $.ajax({
      url:'http://localhost:3000/data2',
      success:function(data){
        console.log(data)
      }
    })

解决方式为

$.ajax({
      url:'http://localhost:3000/data',
      success:function(data){
        console.log(data)
        $.ajax({
          url:'http://localhost:3000/data1',
          success:function(data){
            console.log(data)
            $.ajax({
              url:'http://localhost:3000/data2',
              success:function(data){
                console.log(data)
              }
            })
          }
        })
      }
    })

此方式容易容易造成回调地狱

问题
1.同步和异步的区别
2.手写promise加载图片

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

3.异步使用场景
  网络请求
  定时任务

Promise

promise为对象,它可以获取异步操作的消息。

promise实例

// 实例promise
var p=new Promise(function(resolve,reject){
  // 成功时调用resolve()
  // 失败时调用reject()
  setTimeout(() => {
    var flag=true;
    if(flag){
      // 正常情况
      resolve('hello')
    }else{
      reject('出错了')
    }
  }, 2000);
})
p.then(function(data){
  console.log(data);
},function(info){
  console.log(info);
})

基于promise方式处理ajax调用接口

function queryData(url){
  var p=new Promise(function(resolve,reject){
     var xhr= new XMLHttpRequest();
     xhr.onreadystatechange=function(){
       if(xhr.readyState!=4)return;
       if(xhr.readyState==4&&xhr.status==200){
         //处理正常的情况
         resolve(xhr.responseText)
       }else{
         reject('服务器错误')
       }
     }
     xhr.open('get',url)
     xhr.send(null)
   })
   return p;
 }
 queryData('http://localhost:3000/data')
 .then(function(data){
   console.log(data);
 },function(info){
   console.log(info);
 })

多次ajax请求

// 发送多个ajax请求且保证顺序
queryData('http://localhost:3000/data')
.then(function(data){
  console.log(data);
  return queryData('http://localhost:3000/data1')
})
.then(function(data){
  console.log(data);
  return queryData('http://localhost:3000/data2')
})
.then(function(data){
  console.log(data);
})

接口

const express=require('express')

const app=express()

const cors=require('cors')
app.use(cors())


app.get('/data',(req,res)=>{
  res.send('hello world1')
})
app.get('/data1',(req,res)=>{
  res.send('hello world2')
})
app.get('/data2',(req,res)=>{
  res.send('hello world3')
})

app.listen(3000,()=>{
  console.log('welcome to http://localhost/3000')
})

then参数的打印结果(上一个实例的返回值获得的结果)

  1. 返回上一个对象的结果
  2. 返回上一个对象的字符串,默认的promise

Promise常用API

  • 实例方法
操作说明
.then()得到异步任务的正确结果
.catch()获取异常信息
.finally()成功与否都会执行
function foo(){
  return new Promise(function(resolve,reject){
    setTimeout(() => {
      // resolve(123)
      reject('错误')
    }, 1000);
  })
}
foo(function(data){
  console.log(data);
}).then(function(data){
  console.log(data);
}).catch(function(data){
  console.log(data);
}).finally(function(){
  console.log('finished');
})
  • 对象方法
操作说明
.all()得并发执行所有异步任务,执行完成才得到结果
.race()得并发执行所有异步任务,只要有一个完成就能得到结果
var p1=queryData('http://localhost:3000/a1')
var p2=queryData('http://localhost:3000/a2')
var p3=queryData('http://localhost:3000/a3')

Promise.all([p1,p2,p3]).then(function(result){
  // 得到全部结果
  console.log(result);
})
Promise.race([p1,p2,p3]).then(function(result){
  // 只得到一个结果
  console.log(result);
})

原博客园链接(本人)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值