前端面试题之js一

56 阅读4分钟

一、什么是面向对象,请简述?

面向对象就是将问题抽象成若干个对象,对象中共有的属性和方法可以封装到类中,让它们具有一定的功能。所以类是具有相同或者相似性质对象的抽象。因此类是对象的抽象,对象是类的具体实现。

// 面向对象的核心代码封装起来

    function Person(name, age) {
        this.name = name;//面向对象的属性
        this.age = age;
        this.sleep = function () {//面向对象中的方法
            return 'xxxx'
        }
    }

    // 自己构造的函数
    let obj = new Person('zhangsan', 18);
    console.log(obj);   // {name:'zhangsan',age:18}
    console.log(obj.sleep());

面对对象的三大特点是:封装、继承、和多态

面向对象的优点:易于维护,复用,扩展

面向对象的缺点:性能低,因为创建类的实例需要很大的开销,因为sleep是函数,调用一次就新创建此对象的sleep方法,占内存

二、简述原型、原型链、继承

原型:

1、每一个构造函数都有一个prototype属性,这个属性就是原型对象。该原型对象上的属性和方法可以被该原型对象所属的构造函数实例化的对象访问。

2、JavaScript中,每一个对象都继承自另一个对象,后者通常也被称为原型对象

function Operate(a, b) {
        // 共有的属性
        this.a = a;
        this.b = b;
    }
     console.log(Operate.prototype);
     //结果就是自身的原型对象
        // {constructor: ƒ}
        //constructor: ƒ Operate(a, b)
        // [[Prototype]]: Object

原型链:

1、每一个对象身上都有一个__proto__属性,他所知该对象所在构造函数的原型对象,原型对象也是一个对象,它也存在__proto__,这样一级一级向上指向的规则就形成原型链

image1.png

2、原型链的作用是:在读取对象的某个属性时,js引擎会去寻找自身的属性,如果找不到,就会去这个对象的原型对象上去找,一直找不到就会一直向上找,直到Object.prototype还是找不到,就会返回undefined

继承:

1、继承是发生在两个构造函数或者两个类之间的,如果A构造函数使用B构造函数的属性和方法,我们就说A继承自B,B是父类,A是子类

2、借用父类结构函数继承:我们在子类构造函数中使用call()或者apply()方法调用父类结构函数并改变其this指向为子类构造函数的this。此时子类就可以继承到父类构造函数的属性和父类构造函数中的方法

//继承属性
 // 父类
    function Father(money) {
        this.money = money;
    }

    // 子类
    function Son(name, money) {
        this.name = name;
        // Father(money);  // 只是让Father构造函数执行一遍,其中的this指向依然是父类构造函数的this指向
        Father.call(this,money); //1. 调用了father的构造函数的同时,2. 改变了father的this的指向变成了son的this
    }
    //    1. 在子类构造函数调用父类的构造函数
    //    2. 借用父类构造函数的继承

    let son1 = new Son("小明",10000);
    console.log(son1);
    
    
    
   //继承方法
   function Father() {
   this.prototype.smoke = function () {
        console.log("抽二手烟~~");
    }
   }
    
    function Son(name, money) {
        this.name = name;
        this.money = money;
    }
    Son.prototype = new Father();  // 建立了两个构造函数的关系  这种写法会把Son的prototype的constructor属性给覆盖掉
    Son.prototype.constructor = Son; // 把constructor给找不回来
    Son.prototype.study = function () {
        console.log("学javascript");
    }
    let son1 = new Son("小明", 100)
    console.log(son1);
    son1.study();
    son1.smoke();// 继承过来了

3、由于面对对象的写法是将属性卸载构造函数中,方法写在原型对象上,所以我们一般使用组合继承,也就是通过借用子类构造函数继承父类的属性,原型链继承来继承父类原型上的方法 es6新增的class 类 两个类产生继承 1、子类extends父类 2、使用super(属性列表)访问父类的属性

//组合继承
    /* 
     组合继承:继承了父类的属性(借用了父类的构造函数)和父类原型上的方法(原型链继承)
    */
   class Animal {
       constructor(name,age){
          this.name = name;
          this.age = age;
       }
       eat(){ // 父类原型对象上方法
        console.log("会吃");
       }
    }
    // 狗中有一个特有的属性   hobby
    class Dog extends Animal {
        constructor(name, age, hobby) {  // 1. 这个会new Dog()自动调用   2. this的指向是实例对象   
            super(name,age); // 1. 调用了父类的constructor()  2. 改变了this的指向  3. 使用了父类的属性
            this.hobby = hobby;
        }
        sleep() { //3. Dog的原型对象上的方法  4. this指向也是实例化对象
            console.log("狗会睡觉");
        }
    }

    let dog = new Dog("花花",1,"叫")
    dog.sleep()
    dog.eat() ; // 印证了 继承了父类原型对象上的方法
    console.log(dog);

    var an = new Animal('动物',10);
    an.eat()

三、构造函数和普通函数的区别?

1、返回值的类型不同:

构造函数的默认返回值是this

普通函数的默认返回值是undefined

2、函数名的区别: 构造函数的名字必须和类名一致,要求首字母大写,一般是名词

普通函数的函数名遵循变量的命名规则即可,一般是动词

3、调用方式的区别:

构造函数在调用时使用new 来调用

普通函数在使用时使用函数名()来调用即可

4、作用上的区别:

构造函数目的用于实例化对象,通过对象用构造函数中的方法来完成

普通函数的目的就是单纯的封装一个功能