JS面试

JavaScript部分

1. typeof、instanceof、Object.prototype.toStirng.call()、constructor

  • instanceof的作用:用来判断一个引用是否属于某构造函数。

    这种判断方式也没法判断区分引用类型

    instanceof主要用于判断某个实例是否属于某个类型,也可用于判断某个实例是否是其父类型或者祖先类型的实例。

    instanceof主要的实现原理就是只要右边变量的prototype在左边变量的原型链上即可。因此,instanceof在查找的过程中会遍历左边变量的原型链,直到找到右边变量的prototype,如果查找失败,则会返回false

    let arr=[1,2,3]
    arr instanceof Array     //结果是true
    
    /*instanceof原理*/
    function instanceof(left,right){
        const rightVal=right.prototype
        const leftVal=left.__proto__
        while(true){
            if(leftVal===null){
                return false
            }
            if(leftVal===rightVal){
                return true
            }
            leftVal=leftVal.__proto__  //获取祖类型的__proto__
        }
    }
  • Object.prototype.toString.call(obj)检测对象类型。可以区分开引用类型,说白一点,就是使用这种方法可以完全区分判断所有的数据类型

    console.log(Object.prototype.toString.call("jerry"));//[object String]
    console.log(Object.prototype.toString.call(12));//[object Number]
    console.log(Object.prototype.toString.call(true));//[object Boolean]
    console.log(Object.prototype.toString.call(undefined));//[object Undefined]
    console.log(Object.prototype.toString.call(null));//[object Null]
    console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
    console.log(Object.prototype.toString.call(function(){}));//[object Function]
    console.log(Object.prototype.toString.call([]));//[object Array]
    console.log(Object.prototype.toString.call(new Date));//[object Date]
    console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
    function Person(){};
    console.log(Object.prototype.toString.call(new Person));//[object Object]

​ 实现原理:toString方法返回反映这个对象的字符串,使用toString方***

2 call、apply以及bind的区别和用法

​ 作用:他们最主要的作用,是改变this的指向。

 #### 2.1 call和apply的共同点

​ 共同点,改变上下文环境,将一个对象的方法交给另一个对象来执行,并且是立即执行的。

​ A对象有一个方法,而B对象因为某种原因,也需要用到同样的方法。那么这时候我们单独为B对象扩展一个方法呢,还是借用一下A对象的方法呢。在js中,借用其他对象的方法是允许的。所以借用A对象的方法,即完成了需求,又减少了内存的占用。

2.2 call和apply的区别

​ 主要的区别,主要体现在参数的写法上。

类数组

​ 在js中有一些对象它拥有length属性,且拥有为非负整数的属性(key),但是它又不能调用数组的方法,这种对象被称为类数组对象

  • 拥有length属性,其他属性(索引)为非负整数(对象中的索引会被当做字符串来处理),这里你可以当做是个非负整数串来理解

  • 不具有数组所具有的方法

  • 可以加上Array.prototype的一些有用属性:

    ”push“:"Array.prototype.push"

    "splice":"Array.prototype.splice"

参数写法不同

call和apply主要的区别在参数的写法上不同

call的写法

>  Function.call(obj,param1,param2,param3,....paramN)

​ 调用call的对象,必须是个函数Function

​ call的第一个参数,是 一个对象。Function的调用者,将会指向这个对象。如果不穿,则默认为全局对象window

​ 第二个参数开始,可以接受任意个参数。每个参数会映射到相应位置的Function的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到Function对应的第一个参数上,之后参数为空

function func(a,b,c){

}
func.call(obj,1,2,3)    //func接收到的参数实际上是1 ,2 ,3

func.call(obj,[1,2,3])    //func这次接收到的参数是[1,2,3],undefined,undefined

白话文:使用call的时候传递多个参数的时候用逗号将它们隔开,一个一个的传入

*apply的写法 *

Function.apply(obj[,argArray])

首先他的调用者是Function,第一个参数是需要借用此方法的对象

第二个参数必须是数组或类数组

function func(a,b,c){
}
func.apply(obj,[1,2,3]) //和func.call(obj,1,2,3)的效果是一样的,传入的实际参数是1,2,3

func.apply(obj,{
0:1,
1:2,
2:3,
length:3
})  //这种写法,就是通过类数组进行传递,效果是一样的

call的使用场景主要在实现继承的时候

2.3 bind

​ bind方法与apply和call比较类似,也能改变函数体内的this指向。不同的是,bind方法的返回值是函数,并且需要稍后调用,才会执行,而apply和call是立即调用。

function add(a,b){
    reutrn a+b;
}
function sub(a,b){
    return a-b;
}
//需求:sub对象调用add这个方法,写法如下:
add.bind(sub,12,3) //不会立即执行
add.bind(sub,12,3)()  //会立即执行

//等价于
add.apply(sub,[12,3])
add.call(sub,12,3)

//更加常用的搞法是:
function sub(a,b){
    add.call(this,12,3)
    return a-b
}

![image-20200723104421526](https://uploadfiles.nowcoder.com/images/20190919/56_1568900435177_29C080A5413E925FE3B3CCB4048AB99B Data\Typora\typora-user-images\image-20200723104421526.png)

3 Array.sort()原理

V8引擎sort函数只给出了两种排序:插入排序和快速排序,数组长度小于等于10的时候用插入排序,比10大的数组则使用快速排序

sort()方法用于对数组的元素进行排序,默认是安装每个字符对应的unicode编码的大小进行升序或者降序排序

sort()方法有两种调用方式。

arr.sort(para); //para可选参数

  • 当不传入参数的时候,arr将按照unicode的大小进行排序

  • 如果传入参数,这个参数必须是一个函数,这个函数用来决定排序的方式,以某种方式排序,例如升序或者逆序

    比较函数传入参数(a,b)a,b在原数组中的位置a在b的后面(a在左,b在右)

    • 比较函数返回一个正值,则交换a,b的位置

    • 比较函数返回一个负值,则两者位置不变

    • 返回一个0.则位置不变

      比较函数返回一个大于0的数,则交换a,b位置

    /*实现升序操作*/
    function asc(a,b){
      if(a>b){
          return  1;
      }else if(a < b){
          return -1;
      }else{
          return 0
      }
    }
    /*也等价于下面这种写法*/
    function asc1(a,b){
      return a-b
    }
    
    

/实现降序的操作/
function dsc(a,b){
if(a>b){
return -1;
}else if(a<b){
return 1
}else{
return 0
}
}
/降序操作也等价于下面的写法/
function dsc1(a,b){
return b-a
}

let arr=[2,3,4,12,12,12312312,2,1212,11,2,5,2,41,2]
arr.sort(fucntion(a,b){
})



###  4 JS的垃圾回收

​    JavaScript中的内存管理是自动执行的,而且是不可见的。我们创建基本类型,对象、函数.......所有这些都需要内存

   当不再需要某样东西时会发生什么?JavaScript引擎如何发现并清理它?

  #### 内部算法

1. 基本的垃圾回收算法称为**标记-清除法**,定期执行以下“垃圾回收步骤”“

- 在函数声明一个变量的时候,就将这个变量标记为**进入环境**。而当变量离开环境时标记**离开环境**。 垃圾回收器会将所有的标记离开环境的数据给回收掉

  ```javascript
  test(){
      var a=10;//被标记。进入环境
      var b=20;//被标记,进入环境
  }
  test();//执行完毕之后。a,b被标记离开环境,被回收
  1. 引用计数法

    引用计数的含义是跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型值赋给该变量时,则这个值得引用次数就是1。如果同一个值又被赋给另一个变量,则该值得应用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值得引用次数减1。当这个值得引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存回收回来。

    test(){
        var a={};//a的引用次数为0
        var b=a; //a 的应用次数加1,为1
        var c=a; //a的引用次数再加1,为2
        var b={}; //a的引用次数减1 ,为1
    }
    /*当一个变量的引用次数为0的时候,才会被回收*/

5 addEventlistener和普通onclick区别

addEventlisterner:

   function modifyText() {
     var t2 = document.getElementById("t2");
     if (t2.firstChild.nodeValue == "three") {
       t2.firstChild.nodeValue = "two";
     } else {
       t2.firstChild.nodeValue = "three";
     }
   }

   // add event listener to table
   var el = document.getElementById("outside");
   el.addEventListener("click", modifyText, false);
   function initElement() {
       var p = document.getElementById("foo");
       // NOTE: showAlert(); or showAlert(param); will NOT work here.
       // Must be a reference to a function name, not a function call.
       p.onclick = showAlert;
   };

   function showAlert(event) {
       alert("onclick Event detected!");
   }

6. 函数柯里化

柯里化是指这样一个函数(假设叫做createCurry),他接收函数A作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数A的剩余参数。

7.寻找两个不同元素最近的父节点

   假设现在有两个Node......(好累,不像抄了)

8. 如何给localStorage设置一个过期时间

​ 基础介绍localStorage的使用:

  localStorage.setItem('test',1234567)
  let test=localStorage.getItem('test')
  localStorage.removeItem('test')
  localStorage.clear()
//得到某个索引的key:  localStorage.key(index);

9 页面生命周期

页面生命周期

https://www.cnblogs.com/cc-freiheit/p/12125687.html

  • 1 DOMContented 浏览器已经加载了html ,DOM树已经构建完毕,但是img和外部样式表等资源可能还没有下载完毕

    DOMContentloaded时间有document对象触发,所有的dom节点都创建好之后触发

    这里需要注意的是js的加载顺序

    • 脚本: 浏览器的UI渲染线程和JS引擎是互斥的,当JS引擎执行时UI线程会被挂起。因此,当浏览器在解析HTML时遇到时,将不会继续构建DOM树,转而去解析、执行脚本,所以DOMContentLoaded有可能在所有脚本执行完毕之后触发。但是实际的开发中,一般不会这样做,因为出现这样的情况,白屏时间比较长,用户无法接受。常用的做法:将script标签放在body最后面,让script脚本最后加载或者采用h5的方式,加上defer属性,延迟加载js脚本,或者使用async属性

      async defet
      顺序 带有async的脚本是优先执行先加载完的脚本,即他们在页面中的顺序并不保证他们的顺序。遇到sync属性的script标签,会继续往下解析,并且同时另开进行进程下载脚本,当脚本下载完毕。浏览器停止解析,开始执行脚本,执行完毕后继续往下解析 带有defer的脚本时按照他们在页面中出现的顺序依次执行
      DOMContented 带有async的脚本也许会在页面没有完全加载完之前就加载了,这种情况会在脚本很小或本缓存,并且页面很大的情况下发生 带有defer的脚本会在页面加载和解析完毕后执行,刚好在DOMContentLoaded之前加载(加载完就触发DOMContentLoaded)
  • 2 load 浏览器已经完全加载了所有资源

  • 3 beforeunload 用户即将离开页面

  • 4 unload 用户离开页面

  • 5 readyState document.readState这个只读属性可以告诉程序当前文档加载到哪一个步骤,它三个值:、

    • loading - 加载。document(dom树仍然在构建)仍在加载中;

    • interactive- 互动,文档已经完成加载,文档已经被解析,但是诸如图像,样式表和框架之类的子资源仍然在加载

    • complete: 文档和所有子资源已经完成加载。状态表示load事件即将被触发

      而这个属性的每次改变同样有一个时间可以监听:

      document.addEventListener('readystatechange',()=>console.log(document.readyState))

10 JS加载顺序、执行原理与性能关系

HTML页面渲染过程

  • 开始

    浏览器创建一块新的栈内存用于执行HTML代码,首先创建document对象,开始解析,创建HTMLElement对象,添加到document中,该阶段在documentloaded之前,readyState='loading'

  • 加载css

    解析顺序安装文档的顺序从上往下解析,这个阶段一直是渲染引擎在解析,当碰到head标签时,碰到外部css文件,渲染引擎会为其创建渲染引擎线程加载css。主线程继续解析文档流

  • 加载js脚本

    • 当碰到script标签时,技术的发展,有两种加载形式。同步加载JS脚本,这也是默认的原始方式。因为js引擎和渲染引擎是互斥的,所有当加载js脚本时候,渲染引擎会被阻塞掉,不会再解析文档。当脚本解析完毕之后,才会继续run 渲染引擎,解析文档流。但是这样一般会出现bug,就是脚本中如果有队dom节点的操作的话,会报错,因为文档还没有解析完,dom树没有构建完。所以一般做法是将script脚本放在body的最后面执行,这个时候的dom节点已经构建完成。

    • 异步加载脚本

      在现代技术尤其是在h5中,加入了defer和async属性,加入了延迟加载和异步加载的方式来加载脚本

      其实defer的延迟加载和将脚本放在body部分的最后面是一样的。

  • 加载图像

    解析文档时,遇到img等,会单独创建渲染引擎线程,加载图像,渲染引擎的主线程继续解析文档

  • 当文档解析完成,即文档流将body部分都解析完成,所有的dom节点都已经创建完成。此时DOMContented已经被触发,document.readyState='interactive' .但是这个阶段想图像,音频,视频等资源可能都还没加载完成

  • DOMContented时间的触发标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段

  • 当线程加载完CSS文件后。生成CSSOM,结合DOM生成render tree,然后进行回流与重绘最后显示出来,这个时候页面就显示出来了

  • 当所有img、音频加载完成后,document.readyState='complete', window对象触发load事件

11 为什么setTimeout会出现误差

原因:marcotask中的执行时间大于setTimout中执行时间。如果当前执行栈所花费的时间大于定时器时间,那么定时器的回调会在宏任务里,来不及去调用,所以会产生时间误差

重写set(存入)方法:

/*
@param {[type]} key [键名]
@param{[type]} value[键值]
@param{[type]} days [保存的时间(天)]
*/

set:function(key,value,days){
    if(!value){
        localStorage.removeItem(key)
    }else{
        var Days=days||7 //默认保留7天
        var exp=new Date();
        localStorage[key]=JSON.stringify({
            value,
            expires:exp.getTime()+Days*24*60*60*1000
        })
    }
}
get:function(name){
   try{
       let o=JSON.parse(localStoragep[name])
       if(!o||o.expires<Date.now()){
           return null
       }else{
           return o.value
       }
   }catch(e){
       return localStorage[name]
   }finally{
       //随便写点啥

   }
}

12 this的指向问题

  • 全局环境中的this

    • 浏览器环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this都指向全局对象window
    • node环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部),this都是空对象{}
  • 其他环境下都是,看你在看什么时候调用,就是指向谁,还要注意call apply可以改变上下文环境,就是改变this的指向

  • 特别说明下:箭头函数的情况,箭头函数没有自己的this,继承上下文绑定的this

    let obj={
        age:20,
        info:function(){
            return ()=>{
                console.log(this.age); //this继承的外层上下文绑定的this
            }
        }
    }
    
    let person={age:28}
    let info=obj.info()
    info();//20
    
    let info2=obj.info.call(person)
    info() //28

13 数据类型和javaScript内存

1 js数据类型

常用的六种基本(简单)数据类型
  • Boolean
  • String
  • Number
  • Undefined
  • NULL
  • Symbol
复杂数据类型
  • Object

    Object不是一个对象,这时JS历史遗留问题

2 复杂数据类型和基本数据类型的区别

  • 内存分配不同: 基本数据类型主要存在栈中,复杂数据类型主要存在堆中

    复杂数据类型的还在栈中村放一个地址值,这个地址指向堆中存储的复杂数据类型数据的真实值

  • 访问机制不同:基本数据类型主要是值引用(访问),复杂数据类型主要是地址引用(访问)

  • 赋值是不同(a=b)

    • 基本数据类型:a=b; 将b的值的副本赋给了变量a,两者是完全独立的
    • 复杂数据类型: a=b;将b的地址值给了变量a,最终a和b都指向同一个地址。两者不是完全独立的,如果真实值发生改变,a和b的值也会相应的发生改变
  • 参数传递的不同(实参/形参)

    函数传参:基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址

14 如何让(a==1&&a==2&&a==3)的值为true

1 for of和for in

for of适用于可迭代的对象,数组,map , set, String , TypedArray 但是不适用于一个Object对象 {}

for in 主要用来*遍历Object{} *于Object.keys的用法是一样的

 let onj={a:1,b:2,c:'ss',w:'sbdf',wome:'s'}
 for(let item of onj){
     console.log(onj[item])
 }

2 理解使用ES6中的Symbol

​ 这是一种新的基础数据类型(primitive type),Symbol是由ES6规范引入的一项新特性,它的功能类似于一种标识唯一性的ID.

let s1=Symbol()
let s2=Symbol('another symbol')

/*
需要主要的地方
1. Symbol不能new 出来
2.Symbol的每个实例都是唯一的独一无二的,不存在某个实例A和实例B相等的情况
*/

let s3=new Symbol('I AM BIRD') //错误
let s4=Symbol('s')
let s5=Symbol('s')
s4===s5 //结果为false

应用场景1:使用Symbol来作为对象属性姓名(key)

使用Object.keys和for ... in来遍历对象属性时,不会遍历Symbol属性的值

let obj = {
   [Symbol('name')]: '一斤代码',
   age: 18,
   title: 'Engineer'
}

Object.keys(obj)   // ['age', 'title']

for (let p in obj) {
   console.log(p)   // 分别会输出:'age' 和 'title'
}

Object.getOwnPropertyNames(obj)   // ['age', 'title']

也正因为这样一个特性,当使用JSON.stringify()将对象转换为JSON字符串的时候,Symbol属性也会被排除在输出内容之外:

JSON.stringify(obj)  // {"age":18,"title":"Engineer"}

我们可以利用这一点,来设计我们的数据对象,让对内操作和对外选择输出变得更加优雅

如果要获取Symbol方式定义的对象属性,可以是使用Symbol的api

// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]

// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']

应用场景2:使用Symbol来替代常量

const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()

引用场景3:使用Symbol定义类的私有属性/方法

注册和获取全局Symbol

let gs1 = Symbol.for('global_symbol_1')  //注册一个全局Symbol
let gs2 = Symbol.for('global_symbol_1')  //获取全局Symbol

gs1 === gs2  // true

3 (a==1)&&(a==2)&&(a==3)的值为true

方法一: 利用数据劫持 使用defineproperty

    let i=1;
    Object.defineProperty(window,'a',{
    get:function(){
        return i++;
     }
   })
   console.log(a==1&&a==2&&a==3)

方法二:使用es6的内置对象 proxy

Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)

语法:const p=new Proxy(target, handler)

target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另一个代理)

handler:一个通常以函数为属性的对象

let a=new Proxy({},{
    i:1,
    get:function(){
        return ()=>this.i++;
    }
})
console.log(a==1&&a==2&&a==3);//true

方法三 :数组的toString接口默认调用数据的join方法,重写数组的join方法

let a=[1,2,3]
a.join=a.shift;
console.log(a==1&&a==2&&a==3);//true

15 防抖(debounce)与节流(throttle)函数

  • 防抖:指触发事件后在n秒内函数只执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间

  • 节流: 指连续触发事件但是在n秒钟只执行一次函数。节流会稀释函数的执行效率

    举例说明: 小思最近在减肥,但是她非常贪吃。为此,与其男朋友约定好,如果10天不吃零食,就可以购买一个包(不要问为什么是包,因为包治百病)。但是如果中间吃了一次零食,那么就要重新计算时间,直到小思坚持10天没有吃零食,才能购买一个包。所以,管不住嘴的小思,没有机会买包(悲伤的故事)...这就是防抖

    不管吃没吃零食,每10天买一个包,中间想买包,忍着,等到第十天的时候再买,这种情况是节流。如何控制女朋友的消费,各位攻城狮们,get到了吗?防抖可比节流有效多了!

    作者:刘小夕
    链接:https://juejin.im/post/5cea6e5fe51d45775e33f4de
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

防抖应用场景:

  • 1 搜索框输入查询,如果用户一直在输入中,没有必要不停的调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。

  • 表单验证

  • 按钮提交事件

  • 浏览器窗口缩放,resize事件等。

防抖分为非立即执行版和立即执行版

非立即执行版:是触发事件后函数不会立即执行,而是在n秒后执行,如果在n秒内触发了事件,则会重新计算函数执行时间

立即执行版:是触发事件后函数立即执行,然后n秒内不触发事件才能继续执行函数的效果

   function debounce(func,wait, immediate=true){
       let timeout;
       return function(){
           let context=this;
           let args=arguments;
           if(timeout){
               clearTimeout(timeout)
           }
           if(timeout){
               var callNow=!timeoutl;
               if(immediate){
                   var callNow=!timeout;
                   timeout=setTimeout(()=>{
                       timeout=null;
                   },wait)
                   if(callNow) func.apply(context,args)
               }else{
                   timeout=setTimeout(function(){
                       func.apply(context,args)
                   },wait)
               }
           }
       }
   }

16 Reflect对象

就是一个工具类对象,提供好多静态方法

reflect是一个内置的对象,它提供拦截JavaScript操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

​ reflect对象与proxy对象一样,也是es6为了操作对象而提供的新API.Reflect对象的设计目的有这样就几个

  • 将Object对象的一些明显属于语言内部的方法(比如Objeect.defineProperty),放到reflect对象上
  • 修改某些Object方法的返回结果,让其变得更合理,比如,Object.defineProperty(obj,name,desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj,name,desc)则会返回false
  • 让Object操作都变成函数行为。某些Object操作是命令式,比如 name in obj和delete obj[name],而Reflect.has(obj,name)和Reflect.deleteProperty(obj,name)让它们变成了函数行为。

*Reflect对象一共有13个静态方法 *

1:Reflect.get(target,name.receiver)

​ target:目标对象

name:使我们要读取的属性

recevier(可选):可以理解为上下文this对象

const obj={
    name:'hello',
    age:18,
    get:function(){
        console.log(this.name)
        console.log(this.age)
    }
}
console.log(Reflect.get(obj,'age'));
  1. reflect.set(target,name,value ,recevier)

    设置该对象的属性值

    const obj={
        name:'hello',
        age:18,
        gender:1,
        set:function(gender){
            this.gender=gender
        },
        get:function(){
            console.log(this.name)
            console.log(this.age)
        }
    }
    const res=Reflect.set(obj,'data',31)
    console.log(res)
    

    17 Proxy

    https://www.cnblogs.com/dengxiaoning/p/11681242.html
    什么是'代理',代理:就是调用new创建一个和目标(target)对象一直的虚拟化对象,然该代理中就可以拦截JavaScript引擎内部目标的底层对象的操作;这些底层操作被拦截后回厨房相应的特定操作的陷阱函数

         let tartget = {};
     let proxy = new Proxy(target,{});
     proxy.name = 'proxy';
     console.log(proxy.name); // proxy
     console.log(tartget .name); // proxy
    
     tartget .name = 'tartget';
     console.log(proxy.name); // target
     console.log(tartget .name); // target

    如proxy.name='proxy';将proxy赋值给proxy.name时,代理就会将该操作转发给目标,执行name属性的创建;然而他只是做转发而不会存储该属性;so他们之间存在一个相互引用;target.name设置一个新值后,proxy.name值也改变了

function parent(name){
    this.name=name
}

/*构造继承*/
function son(sonName){
    parent.call(this,sonName)
    /*自己的代码*/
}

/*使用原型链继承*/
function son1(){
    /*自己的代码*/
}
son1.prototype=new Parent()


/*还有组合继承和寄生继承*/
全部评论

相关推荐

牛客83700679...:简历抄别人的,然后再投,有反馈就是简历不行,没反馈就是学历不行,多投多改只要技术不差机会总会有的
点赞 评论 收藏
分享
zzzzhz:兄弟你先猛猛投简历至少三百家,能约到面试就去面。最近可以速成智能小车,智慧家居烂大街的项目,不需要自己写,只需要把里面的代码讲解看明白就行。把其中涉及到的八股文都拿出来单独背一下,我去年找工作就一个智能小车智慧家居找了10k差不多。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务