高频 JS面试题 数据类型和变量、闭包、作用域、原型链、继承

转载自: https://blog.csdn.net/qq_44810886/article/details/125141857?spm=1001.2014.3001.5502

目录

转自:JavaScript、ES6 高频重点面试题 | arry老师的博客-艾编程

一、数据类型和变量

1、JS 有哪些数据类型,如何判断这些数据类型 ?(腾讯、阿里、滴滴、货拉拉、百度、招银、字节)

2、 number 类型表示整数的最大范围(字节)

3、什么是变量提升 ?(腾讯、网易、小米)

4、typeof(NaN) 返回什么 ?(滴滴)

5、 typeof(null) 为什么返回的是 'object'(滴滴)

6、null 和 undefined的区别 ?(同花顺、滴滴)

7、console.log([] == false)的输出结果(同花顺)

9、const、let、var 区别(叠纸、字节)

10、 const 定义的值一定是不能改变的吗?(滴滴)

11、 const 声明了数组,还能 push 元素吗,为什么?(阿里)

12、JS 获取字符串的第 N 个字符(网易)

13、const 声明生成对象的时候,如何使其不可更改(字节)

14、 这两种方式的区别 ?typeof 判断(字节)

二、闭包 、作用域 

1、说一下对闭包的理解 ?(快手、滴滴、58 篇、字节、小米、腾讯)

2、你在实际项目中遇到过哪些闭包的坑 ?(腾讯、字节)

3、闭包的好处,闭包里面的变量为什么不会被回收(网易)

4、 JS 作用域和作用域链(小米、腾讯、商汤)

5、 说一下 ES6 的块级作用域(百度)

6、有哪些类型的作用域(字节)

7、怎么理解 JS 静态作用域和动态作用域(小米)

8、 以下代码输出的结果是 ?(小米) 

三、原型链 

1、说说你对原型链的理解 ?(腾讯、货拉拉、字节、招银、阿里、小米)

2、原型链的终点是什么?(货拉拉)

3、es6 class 怎么设置原型、静态、实例方法 ?(阿里)

4、 []的原型链是什么 ?(腾讯)

四、继承

1、JS 的继承有几种方式 ?是怎么实现的?(腾讯、百度)

2、在 JS 中如何实现多重继承 ?(腾讯)

3、对象继承的方法 (网易)

4、 怎么实现类的继承 (网易)

一、数据类型和变量

1、JS 有哪些数据类型,如何判断这些数据类型 ?(腾讯、阿里、滴滴、货拉拉、百度、招银、字节)

JS 的数据类型分为基数据类型和复杂数据类型(引用数据类型)

  • 基础数据类型有:number,string,null,undefined,boolean,symbol(ES6新增)BigInt
  • 复杂数据类型有:array,object,function

1. 用typeof可以判断基础数据类型,但是typeof null返回的是object

  • typeof 的返回值类型为字符串类型
  • typeof 判断基本数据类型时,除了 null 的输出结果为'object' 其它类型都能正确判断
  • typeof 判断引用数据类型时,除了判断函数会输出'function' 其它都输出'object'2. 2.

2.instanceof可以判断 引用数据类型:array,object,function

  • instanceof 用来判断两个对象是否属于实例关系,通过这种关系来判断对象是否属于某一类型。(但不能判断对象具体属于哪种类型)。
  • instanceof 可以准确判断引用数据类型,它的原理是:检测构造函数的 prototype 属性是否在某个实例对象的原型链上。
  • instanceof 返回值为布尔值

   

3. constructor

详细解读

语法:


  
  
  1. "". constructor === String; // true
  2. var num = 1;
  3. num. constructor === Number; // true
  4. true. constructor === Boolean; // true
  5. []. constructor === Array; // true
  6. var obj = {};
  7. obj. constructor === Object; // true

当一个函数 F 被定义时,JS 引擎会为 F 添加 prototype 原型,然后在 prototype 上添加了一个 constructor 属性,并让其指向 F 的引用


  
  
  1. <script>
  2. function F( ) {}
  3. const f = new F(); // 实例
  4. console. log(F. prototype. constructor); // function F(){}
  5. console. log(F. prototype. constructor === F); // 构造器指向F
  6. console. log(f. constructor === F);
  7. </script>

当执行 const f = new F() 时,F 被当成了构造函数,f 是 F 的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此f.__proto__.constructor===F简写成f.constructor === F

从上面的整个过程来看,构造函数 F 就是新对象 f(实例)的类型。所以如果某个实例的 constructor 指向某个构造函数,那这个构造函数就是这个实例的类型。

注意

  • constructor 是不稳定的,因为开发者可以重写 prototype,重写后,原有的 constructor 引用会丢失,需要我们重新指定 constructor 的引用
  • 在没有重新指定时,constructor 会默认为 Object

为什么重写 prototype 后,constructor 的默认值会为 Object 呢?


  
  
  1. <script>
  2. function F( ) {}
  3. F. prototype = {};
  4. console. log(F. prototype. constructor);
  5. // 结果为 Object() { [native code] }
  6. </script>

当 F.prototype={ }时,{ }是 new Object 的字面量(Object 的实例),所以 Object 原型上的 constructor 会被传递给{ },Object 的 constructor 指向的就是 Object 本身

4. Object.prototype.toString.call()

详细解读

toString()是 Object 的原型方法,调用该方法,默认返回当前对象的 [object type]。其中 type 就是对象的类型。

Object对象,直接调用toString() 就能返回 [object Object]

其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息

  • 
        
        
    1. Object. prototype. toString. call( ""); // [object String]
    2. Object. prototype. toString. call( 1); // [object Number]
    3. Object. prototype. toString. call( true); // [object Boolean]
    4. Object. prototype. toString. call( Symbol()); // [object Symbol]
    5. Object. prototype. toString. call( undefined); // [object Undefined]
    6. Object. prototype. toString. call( new Function()); // [object Function]
    7. Object. prototype. toString. call( new Date()); // [object Date]
    8. Object. prototype. toString. call([]); // [object Array]
    9. Object. prototype. toString. call({}); // [object Object]
    10. Object. prototype. toString. call( document); // [object HTMLDocument]
    11. Object. prototype. toString. call( window); // [object Window]

2、 number 类型表示整数的最大范围(字节)

如果是整数的范围的话,JavaScript 能够准确表示的整数范围在-2^53 到 2^53 之间

console.log(Number.MAX_VALUE)

【每日一题】JS 中 Number 类型的可以表示的范围是多少_fe_lucifer的博客-CSDN博客

3、什么是变量提升 ?(腾讯、网易、小米)

变量声明会提升带一段代码的最前面。所以变量声明可以放在对变量操作的后面不会报错。(自己瞎说的不准确啊)

是指使用 var 关键字声明的变量会自动提升到当前作用域的最前面。不过只会提升声明,不会提升其初始化。

变量只有被声明后,才能使用

  • 我们在 var a=10; 之前 console.log(a); 之所以不会报错,就是因为变 a 的声明被提前到了当前作用域的最顶部
  • 不过只提升了声明,并没会提升初始化,所以打印结果为 undefined; (变量声明初始化,其默认值是 undefined)

补充

  • 函数声明也会被提升,函数和变量相比,会被优先提升。
  • 这意味着函数会被提升到更靠前的位置,如果出现了重名的变量和函数,声明提升时会以函数为主。
  • 
        
        
    1. <script>
    2. console. log(num); // function num(){console.log('函数');} 函数被优先提升
    3. var num = 1;
    4. console. log(num); // 1 在从上往下执行时num变量赋值为 1
    5. function num( ) {
    6. console. log( "函数");
    7. }
    8. console. log( num()); // 报错,因为变量num被重新赋值为1,不会再有函数了
    9. </script>

4、typeof(NaN) 返回什么 ?(滴滴)

Number

5、 typeof(null) 为什么返回的是 'object'(滴滴)

涉及到typeof的原理

答案解析

  • typeof(null) = object 是 JS 在诞生设计之初留下的历史遗留 BUG 问题
  • 在 JS 中进行数据底层存储的时候是用二进制存储的,这是一定的,而且它的前三位是代表存储的数据类型,而 000 是代表 object 类型也就是引用类型的数据。
  • 而 null 正好全是 0,所以它巧妙的符合 object 类型的存储格式,所以在 typeof 检测的时候,它才会输出 object。

6、nullundefined的区别 ?(同花顺、滴滴)

答案解析

  • undefined(未定义):当一个变量被定义(声明)但并没有赋值时,他的初始值就是 undefined。
  • null(空):表示对一个空对象的引用。
    • 当一个变量定好之后,未来是用来保存对象的引用时,我们可以给他赋初始值为 null。
    • 当一个对象使用完,需要对其进行释放内存时,可以将其值设置 null (js 会自动垃圾回收)

相同点

  • undefined 和 null 都是基本数据类型,保存栈中。
  • undefined 和 null 转换为 boolean 布尔值都为 false

不同点:

两者转换为数字类型时,其值不一样


  
  
  1. Number( undefined); //NaN
  2. Number( null); //0

特殊点:

undefined == null; //true
  
  

7、console.log([] == false)的输出结果(同花顺)

只有0,-0,NaN,“”,null,undefined(有六种)转换为布尔值是false

==存在隐式转换

1.当一个值是布尔值时,先将该布尔值转换为数值,false转换为0

2.再根据当一个值为数值时,将另外一个也转换为数值,[]转换为数值为0
3.所以相当于console.log(0==0),
结果为true

集体规则见下

8、== 和 === 的区别?(滴滴)

==的隐式转换规则
1.若其中一个数据类型为数值,那么另外一个数据类型会先转换为数值再与之比较

1)字符串与数值


  
  
  1. <script>
  2. console. log( " " == 1); //false
  3. console. log( " " == 0); //true
  4. console. log( "hello" == 1); //false
  5. console. log( "000" == 0); //true
  6. console. log( "666" == 666); //true
  7. //注意"hello" 转换为数字为NaN
  8. </script>

2)布尔值与数值


  
  
  1. console. log( true== 1); //true
  2. console. log( true== 0); //false
  3. console. log( false== 0); //true

3)数组与数值


  
  
  1. console. log([]== 0) //true
  2. console. log([ 1]== 1); //true
  3. console. log([ "1"]== 1); //true
  4. console. log([ 1, 2]== 1); //false
  5. console. log([ true]== 1); //false

注意:数组会先通过调用toString()转换为字符串后再转换为数组,例如:

[true]转换为字符串后为"true",然后再转换为数值是NaN,所以console.log([true]==1)返回false,即:对象 --> 字符串 --> 数值

注意:[]转换为字符串是0

4)特例

  • null==undefined为true
  • null和undefined不会进行数据转换
  • NaN不与任何值相等,包括他自己

  
  
  1. console. log( null== undefined); //true
  2. console. log( null== 0); //false
  3. console. log( null== false); //false
  4. console. log( null== ""); //false
  5. console. log( null== NaN); //false
  6. console. log( undefined== 0); //false
  7. console. log( undefined== ""); //false
  8. console. log( undefined== NaN); //false
  9. console. log( NaN== NaN); //false

2.若其中一个值为布尔值,则会先将其转换为数值再进行比较


  
  
  1. console. log( true== "001"); //true
  2. console. log( true==[]); //false
  3. console. log( true==[ "true"]); //false
  4. console. log( true==[ true]); //false
  5. console. log( true==[ 1]); //true

先将true转换为数值1,再用上面的办法

3." !"为逻辑非,在操作非布尔值类型的数据时,会将该数据类型先转换为布尔值后再取反

注意:只有六个值转换为布尔值为false,0,-0,NaN,false,""


  
  
  1. console. log(! ""); //true
  2. console. log(! "false"); //false
  3. console. log(! 2); //false
  4. console. log(![]); //false
  5. console. log(![ "false"]); //false
  6. console. log(! NaN); //true

通过以上判断[]==![]

==存在隐式转换

===两边的数据类型完全一样才会返回true

== 在比较类型不同的变量时,如果两侧的数据类型不同,则会按以下规则进行相应的隐式类型类型转换

  • 对象 --> 字符串 --> 数值
  • 布尔值 --> 数值

=== 在比较时,会比较值和类型两个。只要两边值的类型不相等就返回 false


  
  
  1. var x = 2;
  2. var y = "2";
  3. (x == y)(
  4. // 返回 true,因为 x 和 y 的值相同
  5. x === y
  6. );
  7. // 返回 false,因为 x 的类型是“数字”,y 的类型是“字符串”

不过要注意以下几个特殊情况的比较


  
  
  1. NaN === NaN; // false NaN和任何数据都不相等,包括自身
  2. [] == []; // false 比较的是地址
  3. {} == {}; // false 比较的是地址
  4. undefined == null; // true; 特殊情况,记下

对象转字符串,得到的是'[object Object]'

考题 1:以下输出结果

console.log([] == false); // true

  
  
  • [] 转换成字符串是'' ,然后'' 转换成数值是 0
  • false 转换成数值是 0 所以最后比较的值是 0==0 ,结果为 true

考题 2:以下输出结果


  
  
  1. if ([]) {
  2. alert( "能弹出吗?"); // 可以弹出弹窗
  3. }
  • if 语句中的表达式或值都会被转成 boolean
  • [] 转成布尔值是 true,所以可以弹出。

9、const、let、var 区别(叠纸、字节)

1.var声明的变量不存在块级作用域

2. let

声明的变量存在块级作用域

不能用let重复声明同一个变量

3.const

存在块级作用域

一般用来声明常量,

且声明是需要初始化,

定义的常量不能改变

(以上是自己回答的)

答案解析

const、let、var 三者的区别,我们可以从以下 5 个点来展开对比

  • 变量提升和暂时性死区: var 存在变量提升,let 和 const 不存在变量提升,所以 let 和 const 会存在暂时性死区
  • 块级作用域: var 不存在块级作用域,let 和 const 存在块级作用域
  • 重复声明: var 允许重复声明变量,let 和 const 在同一作用域下不允许重复声明变量
  • 修改变量: var 和 let 声明的变量可以修改,const 是不可以的。
  • 使用:const 用来声明常量,引用类型值。其它情况推荐用 let ,避免用 var

10、 const 定义的值一定是不能改变的吗?(滴滴)

不是,如果是定义了一个引用数据类型,是可以改变的(自己的回答)

  • const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个栈内存地址所保存的数据不得改动。
  • 对于简单类型的数据(数值、字符串、布尔值)值就保存在变量指向的那个栈内存地址,因此等同于常量。
  • 引用类型的数据(主要是对象和数组)变量指向的栈内存地址,保存的只是一个指向实际数据的指针
  • const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。

所以如果是 const 声明的是一个引用类型的变量,其引用类型的结构是可以发生改变的。

11、 const 声明了数组,还能 push 元素吗,为什么?(阿里)

可以

  • 因为 const 声明的变量保存的只是栈内存中的地址,只是一个指向实际数据的指针。指针指向堆内存中保存的数据。
  • const 只能保证栈内存中的地址不变,但是堆内存中的数据如何改变是没有办法控制的。
  • push 方法相当于是改变了堆内存中的数据结构。

12、JS 获取字符串的第 N 个字符(网易)

字符串操作,

13、const 声明生成对象的时候,如何使其不可更改(字节)

14、 这两种方式的区别 ?typeof 判断(字节)


  
  
  1. const str1 = "abc";
  2. const str2 = new String( "abc");

第一种是返回string

第二种是返回object

答案解析

 

  
  
  1. const str1 = "abc";
  2. const str2 = new String( "abc");

str1 是基本数据类型

  • 存储在在栈内存中,用 typeof 检测的结果为 string。
  • 当我们把 str1 赋值给别一个变量时,是把 str1 中的值复制一份来赋值。(浅拷贝)

str2 是引用数据类型

  • 存储在堆内存中,不过变量 str2 中存的是指向堆内存中的地址,用 typeof 检测 str2 结果为 Object。
  • 当我们把 str2 赋值给另一个变量时,是把 str2 中存入的地址复制一分给到了变量。(深拷贝)

二、闭包 、作用域 

1、说一下对闭包的理解 ?(快手、滴滴、58 篇、字节、小米、腾讯)

函数内嵌套一个匿名函数,匿名函数可以访问到外面函数的变量

2、你在实际项目中遇到过哪些闭包的坑 ?(腾讯、字节)

TIP

  • 不能滥用闭包,否则会造成网页的性能问题,严重时可能会导致内存泄漏
  • 所谓内存泄漏是指程序中已动态分配的内存由于某种原因未释放或无法释放
  • 当然,所谓的内存泄漏是在很多低级别的浏览器(比如 IE 中)中才会出现,高版本的浏览器基本不存在
  • 这也是面试题中常考的题目

3、闭包的好处,闭包里面的变量为什么不会被回收(网易)

闭包好处:


  
  
  1. <script>
  2. function checkWeight( weight) {
  3. return function ( _weight) {
  4. weight > _weight ? alert( "过胖") : alert( "ok达标");
  5. };
  6. }
  7. var P1 = checkWeight( 100); // 调用完毕,作用域和变量weight不会被销毁
  8. P1( 110); // 调用完毕,作用域和变量_weight会被销毁
  9. </script>
  • 如果我们在最后加上P1 = null,则垃圾回收器回在下一次清理内存时
  • 销毁掉 checkWeight 调用形成的作用域和作用域中的变量 weight。

答案解析

这里我们要明确一个点,如果闭包函数的引用计数为 0 时,函数就会释放,它引用的变量也会被释放。

  • 只有当闭包函数的引用计数不为 0 时,说明闭包函数随时有可能被调用,他被调用后,就会引用他在定义时所处的环境的变量。
  • 闭包中的变量就得一直需要在内存中,则就不会被垃圾回收掉。
  • 闭包的功能:记忆性、模拟私有变量#

4、 JS 作用域和作用域链(小米、腾讯、商汤)

作用域:是指函数和变量可访问的范围

作用域链:当访问一个对象时,如果在当前作用域链没有找到,就向当前作用域链的上一级去寻找,这样的查找过程形成了作用域链(自己的回答)

答案解析

要回答这个问题 ,我们可以从以下几个方面来展开讲解:

  • 什么是作用域 ?
  • js 中作用域的分类 ?
  • 每种作用域的特点 ?(作用或创建 ,销毁,变量和函数访问权限)
  • 什么是作用域链 ?
  • 什么是作用域链查找 ?

什么是作用域 ?

  • 作用域就是代码的执行环境。执行环境定义了变量或函数有没有权访问其他数据。
  • 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
  • 虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

js 中作用域的分类 ?

  • 全局作用域
  • 局部作用域(函数作用域)
  • 块级作用域

每种作用域的特点 ?(作用或创建 ,销毁,变量和函数访问权限)

什么是作用域链 ?

当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)

  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
  • 作用域链中的下一个对象来自于外部环境,再下一个变量对象则来自下下一个外部环境,一直到全局执行环境
  • 全局执行环境的变量对象始终都是作用域链上的最后一个对象

什么是作用域链查找 ?

内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。

  • 在内部函数中,需要访问一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续沿作用域链往上查找
  • 如果在某个变量对象中找到则使用该变量对象中的变量值,如果没有找到,则会一直找到全局作用域。如果最后还找不到,就会报错。

5、 说一下 ES6 的块级作用域(百度)

let和const定义的变量具有块级作用域,一般是{}内(自己的回答)

使用 let 或 const 关键字声明的变量,会形成块级作用域。

  • 在 {}、if 、for 里用 let 来声明变量,会形成块级作用域。{} 之外是不能访问 {} 里面的内容。
  • 块级作用域中定义的变量,在 if 或 for 等语句执行完后,变量就会被销,不占用内存

  
  
  1. {
  2. let a = 1;
  3. }
  4. console. log(a); // 会报错,{}里是块级作用域,外面是访问不到里面的变量的

  
  
  1. <script>
  2. for ( let i = 0; i < 3; i++) {
  3. console. log(i); // 0 1 2
  4. }
  5. console. log(i); // i is not defined
  6. </script>

注意点:

对象的 { } 不会形成块级作用域

6、有哪些类型的作用域(字节)

JS 中有 3 种类型的作用域:

  • 全局作用域
  • 局部作用域(函数作用域)
  • 块级作用域

7、怎么理解 JS 静态作用域和动态作用域(小米)

答案解析

  • 静态作用域:又称词法作用域,是指作用域在词法阶段就被确定了(函数定义的位置就决定了函数的作用域)不会改变,javascript 采用的是词法作用域。
  • 动态作用域:函数的作用域在函数调用时才决定的。

  
  
  1. <script>
  2. var a = 1;
  3. function fn( ) {
  4. console. log(a);
  5. }
  6. function test( ) {
  7. var a = 2;
  8. fn();
  9. }
  10. test(); // 1
  11. </script>

最终输出的结果为 1

说明 fn 中打印的是全局下的 a ,这也印证了 JavaScript 使用了静态作用域。

静态作用域执行过程

当执行 fn 函数时,先从内部的AO对象查找是否有a变量,如果没有,沿着作用域链往上查找(由于JavaScript是词法作用域),上层为全局GO,所以结果打印1

8、 以下代码输出的结果是 ?(小米) 


  
  
  1. Object. prototype. a = 10;
  2. var s = Symbol();
  3. var obj = {
  4. [s]: 20,
  5. b: 30
  6. }
  7. Object. defineProperty(obj, 'c', {
  8. enumerable: true
  9. value: 40
  10. })
  11. for( let var in obj) {
  12. console. log(val)
  13. }

以上输出的结果为:b c a

for...in语句以任意顺序遍历一个对象的可枚举属性(除 Symbol 类型的属性)

三、原型链 

1、说说你对原型链的理解 ?(腾讯、货拉拉、字节、招银、阿里、小米)

每个对象都有一个__proto__属性,这个属性本身是一个对象

每个函数都有一个prototype属性,这个属性本身也是一个对象

寻找一个对象的属性和方法时,如果这个对象自身没有这个属性和方法,就顺着__proto__去它的原型上找,如果原型上没有,就去原型的原型上找,一直找到最顶层(Object.prototype)(自己的回答)


  
  
  1. <script>
  2. /*Person 构造函数*/
  3. function Person( name, age) {
  4. this. name = name;
  5. this. age = age;
  6. }
  7. /*构造函数的原型上添加方法*/
  8. Person. prototype. sayHello = function ( ) {
  9. console. log( `大家好,我是${this.name}今年${this.age}岁了`);
  10. };
  11. /*构造函数的原型上添加方法*/
  12. Person. prototype. study = function ( ) {
  13. console. log( `我${this.name}要学习了`);
  14. };
  15. let p1 = new Person( "小明", 23); // p1为 构造函数Person new出来的实例对象
  16. /*实例对象上的属性会屏蔽(遮蔽)原型上同名的属性*/
  17. p1. study = function ( ) {
  18. console. log( `我${this.name}正在学习web前端开发课程`);
  19. };
  20. </script>
  • 原型对象默认拥有一个 constructor 属性,指向它的构造函数
console.log(Person.prototype.constructor === Person); // true

  
  
  • 每个对象实例都有一个隐藏的属性__proto__,被称为隐式原型,指向它的构造函数的原型
console.log(p1.__proto__ === Person.prototype); // true

  
  
  • 对象实例可以共享原型上面的所有属性和方法
p1.sayHello(); // 大家好,我是小明今年23岁了

  
  
  • 实例自身的属性会屏蔽(遮蔽)原型上同名的属性,实例上没有的属性就会去原型上去找
p1.study(); // 我小明正在学习web前端开发课程
  
  

详细解读

  • 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果这个对象本身没有这个属性时,它就会去他的__proto__隐式原型上去找(即它的构造函数的 prototype)。
  • 如果还找不到,就去原型的原型(即构造函数的prototype的__proto__)上去找,....一直找到最顶层(Object.prototype)为止。
  • 如果还没有找到,则返回 undefined。

2、原型链的终点是什么?(货拉拉)

原型链的终点是 null ,因为 Object.prototype.__proto__ 指向 null

3、es6 class 怎么设置原型、静态、实例方法 ?(阿里)

ES6中Class类构造函数、实例、原型属性和方法、继承extends、super关键字_The..Fuir的博客-CSDN博客_class 原型属性

4、 []的原型链是什么 ?(腾讯)

答案解析

  • [].__proto__ 指向 Array.prototype
  • Array.prototype.__proto__ 指向 Object.prototype
  • Object.protytype.__proto__ 的最终指向为 null

  
  
  1. let arr = [ 1, 2, 3];
  2. console. log(arr. __proto__ === Array. prototype); // true 所有数组都是由Array构造出来
  3. console. log( Array. prototype. __proto__ === Object. prototype); // true Array构造函数的是由 Object构造出来的。
  4. console. log( Object. prototype. __proto__); // null Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型

[] 的原型链结构图

 

四、继承

1、JS 的继承有几种方式 ?是怎么实现的?(腾讯、百度)

1.原型链继承

让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承


  
  
  1. <script>
  2. function A( name, age) {
  3. this. name = name;
  4. this. age = age;
  5. this. habby = [ '喵喵叫', '吃罐头']
  6. }
  7. A. prototype. run = function( ) {
  8. console. log( this. name + this. age);
  9. }
  10. function B( ) {}
  11. B. prototype = new A( 'pipi', 1) //A的实例化
  12. let Ba = new B()
  13. let Bb = new B()
  14. console. log( Ba); //B {}
  15. console. log( Ba. name); //pipi
  16. console. log( Ba. habby); // ['喵喵叫', '吃罐头']
  17. Bb. habby. push( '酷跑')
  18. console. log( Ba. habby); // ['喵喵叫', '吃罐头', '酷跑']
  19. console. log( Bb. habby); //['喵喵叫', '吃罐头', '酷跑']
  20. Bb. name = 'wuyi';
  21. console. log( Ba. name); //pipi
  22. console. log( Bb. name); //wuyi
  23. Ba. run() //pipi1
  24. Bb. run() //wuyi1
  25. </script>

缺点:

  1. 由于所有Child实例原型都指向同一个Parent实例,引用类型的私有属性公有化了, 因此对某个Child实例的父类引用类型做变量修改会影响所有的Child实例
  2. 在创建子类实例时无法向父类的构造函数传参(没有 super 功能)

2.构造函数继承

原理:子类的构造函数中执行父类的构造函数,并为其绑定子类的 this,让父类的构造函数把成员属性和方法都挂到子类的 this 上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参


  
  
  1. <script>
  2. function A( name, age) {
  3. this. name = name;
  4. this. age = age;
  5. this. habby = [ '喵喵叫', '吃罐头']
  6. }
  7. A. prototype. run = function( ) {
  8. console. log( this. name + this. age);
  9. }
  10. function B( name, age) {
  11. A. call( this, name, age)
  12. }
  13. let Ba = new B( 'pipi', 1)
  14. let Bb = new B( 'wuyi', 0.5)
  15. Bb. habby. pop()
  16. console. log( Ba);
  17. console. log( Bb);
  18. console. log( Ba. run());
  19. </script>

缺点:继承不到父类原型上的方法(所以不能单独使用咯 ~)

3.组合继承

原理:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性(构造函数+原型对象


  
  
  1. <script>
  2. function A( name, age) {
  3. this. name = name;
  4. this. age = age;
  5. this. habby = [ '喵喵叫', '吃罐头']
  6. }
  7. A. prototype. run = function( ) {
  8. console. log( this. name + this. age);
  9. }
  10. function B( name, age) {
  11. A. call( this, name, age)
  12. }
  13. B. prototype = new A()
  14. let Ba = new B( 'pipi', 1)
  15. let Bb = new B( 'wuyi', 0.5)
  16. Bb. habby. pop()
  17. console. log( Ba);
  18. console. log( Bb);
  19. console. log( Ba. run());
  20. console. log( Bb. run());
  21. </script>

组合式继承的缺点:每次创建子类实例都执行了两次构造函数(Parent.call() 和 new Paren()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法

4.寄生式继承

5.寄生式组合继承


  
  
  1. <script>
  2. function A( name, age) {
  3. this. name = name;
  4. this. age = age;
  5. this. habby = [ '喵喵叫', '吃罐头']
  6. }
  7. A. prototype. run = function( ) {
  8. console. log( this. name + this. age);
  9. }
  10. function B( name, age) {
  11. A. call( this, name, age)
  12. }
  13. // B.prototype = new A()
  14. //寄生式组合继承
  15. B. prototype = Object. create(A. prototype)
  16. console. log(B. prototype. __proto__ == A. prototype); //true
  17. console. log(A. prototype);
  18. let Ba = new B( 'pipi', 1)
  19. let Bb = new B( 'wuyi', 0.5)
  20. Bb. habby. pop()
  21. console. log( Ba);
  22. console. log( Bb);
  23. console. log( Ba. run());
  24. console. log( Bb. run());
  25. </script>

2、在 JS 中如何实现多重继承 ?(腾讯)

3、对象继承的方法 (网易)

4、 怎么实现类的继承 (网易)


  
  
  1. <script>
  2. // Class 类继承
  3. class Father {
  4. constructor( surname) {
  5. this. surname = surname; // 类的属性声明用 this 即可
  6. }
  7. saySurname( ) {
  8. console. log( 'My surname is ' + this. surname); // 访问类的属性要用 this
  9. }
  10. }
  11. class Son extends Father { // 这样子类就继承了父类的属性和方法
  12. constructor( surname, firstname) {
  13. super(surname); // 通过调用 super 来调用父类的构造函数,并初始化父类的属性
  14. this. firstname = firstname; // 初始化一个子类属性
  15. }
  16. sayFirstname( ) {
  17. console. log( 'My firstname is ' + this. firstname);
  18. }
  19. }
  20. const cc = new Son( 'ai', 'huohuo');
  21. cc. saySurname(); // My surname is ai
  22. cc. sayFirstname(); // My firstname is huohuo
  23. </script>

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值