前端面试题总结

1、预编译习题

function fn(a,c){
	console.log(a); // function a(){}
	var a = 123
	console.log(a) // 123
	console.log(c) // function c(){}
	function a(){}
	if(false){
		var d = 678
	}
	console.log(d) // undefined
	console.log(b) // undefined
	var b = function(){}
	console.log(b) // function b(){}
	function c(){}
	console.log(c) // function c(){}
}
fn(1,2);


AO:{
	a:undefined 1 function a(){} 123
	c:undefined 2 function c(){}
	d:undefined
	b:undefined
}

预编译过程:

  1. 创建了AO对象
  2. 找形参和变量的声明 作为AO对象的属性名,值是undefined
  3. 实参和形参相统一
  4. 找函数声明,要是函数名和变量名一样,会覆盖变量的声明

2、this大厂面试题

    var name = 222;
    var a = {
        name: 111,
        say: function () {
            console.log(this.name);
        }
    };

    var fun = a.say;
    fun(); // 222
    a.say(); // 111 

    var b = {
        name: 333,
        say: function (fun) {
            fun(); // fun.call(window)  --> 222
        }
    };

    b.say(a.say); 
    b.say = a.say
    b.say(); //333

3、箭头函数的this

  • 箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定。
  • 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
  • 箭头函数中的this是在定义函数的时候绑定
var x = 11;
var obj = {
	x:22,
	say:()=>{
		console.log(this.x); // 输出11
	}
}
obj.say();
  • 所谓的定义时候绑定,就是this是继承自父执行上下文中的this,比如这里的箭头函数T中的this.x 身与say平级以key:value的形式,也就是箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.x实际上表示的是window.x,因此输出的是11。

4、js深浅拷贝

  1. 浅拷贝 深拷贝
  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性的一份精确拷贝。

如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
  1. 浅拷贝 和 赋值的区别
    • 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
    • 浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型共享同一块内存,会互相影响
    • 深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

浅拷贝

function shallowCopy(){
	var target = {};
	for(var i in obj){
		if(obj.hasOwnProperty(i)){
			target[i] = obj[i]
		}
	}
	return target
}

深拷贝

function deepClone(obj){
	var cloneObj = new obj.constructor()
	//var cloneObj = {}
    if(obj === null) return obj
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExg(obj)
    if(typeof obj !== 'object') return obj;
	for(var i in obj){
		if(obj.hasOwnProperty(i)){
			cloneObj[i] = deepClone(obj[i])
		}
	}
	return cloneObj
}

a instanceof b ——》 b的prototype是否在a的原型链上

  • 浅拷贝的实现方式
    • Object.assign()
    • lodash里面的_.clone
    • …展开运算符
    • Array.prototype.concat
    • Array.prototype.slice
  • 深拷贝的实现方式
    • JSON.parse(JSON.stringify())
    • 递归的操作
    • lodash里面的cloneDeep
    • JQuery.extend()
拷贝和原数据是否指向同一对象第一层数据为一般数据类型第一层数据不是原始数据
赋值改变会使原始数据一同改变改变会使原始数据一同改变
浅拷贝改变不会使原始数据一同改变改变会使原始数据一同改变
深拷贝改变不会使原始数据一同改变改变不会使原始数据一同改变

5、图片懒加载

将图片的src属性置为空,将路径放在data-src里面,最后判断 图片距离浏览器顶端的距离小于视口高度+滚动条高度 就将data-src赋值给src

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzLpwHyW-1622274262861)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527101250448.png)]

6、js作用域

  1. 全局作用域

    • 全局作用域在页面打开时被创建,页面关闭时被销毁
    • 编写在script标签内的变量和函数,作用域为全局,在页面的任何位置都可以被访问到。
  2. 函数作用域

    • 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
    • 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的

执行期的上下文

  • 当函数代码执行前期,会创建一个执行期上下文的内部对象 AO(作用域)
  • 这个内部的对象是预编译的时候创建出来的,因为当函数被调用的时候,会进行预编译
  • 在全局代码执行前期会创建一个执行期的上下文的对象GO

函数作用域预编译

  1. ​ 创建AO对象 AO{}
  2. 找形参和变量声明 将变量和形参名当做AO对象的属性名值为undefined
  3. 实参形参相统一
  4. 在函数体里面找到函数声明,值赋予函数体

全局作用域的预编译

  1. 创建GO对象
  2. 找变量声明,将变量名作为GO对象的属性名 值是undefined
  3. 找函数声明,值赋予函数体

7、闭包的底层理解

  • 作用域链
    • 会被保存到一个隐式的属性中去 [ [ scope ] ] 这个属性是我们用户访问不到的 但是的的确确是存在的 让js引擎来访问的,里面存储的就是作用域链
var global;
function a(){
	function b(){
		var bb = 123
		aa  = 0;
	}
	var aa = 123
	b()
}
a()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bOcdDrmz-1622274262863)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527105722185.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9RYdyXCV-1622274262864)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527110219041.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-253z57Yf-1622274262867)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527110432766.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ma84eLaM-1622274262868)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527110450510.png)]

function a(){
	var aa = 123
	function b(){
		var bb = 234
		console.log(aa)
	}
	return b
}
var res = a()
res()

当res()函数执行的时候,b函数被保存到了外部,但是为什么又能拿到a函数中的变量呢?

因为当b函数被定义的时候,它能拿到a中的变量(因为有上图作用域的存在),a执行完毕,只需要把下图中的线剪掉,但是b没有被销毁,因为被保存到了外部,所以就不会被剪掉其他的线,这就是闭包的底层原理。因为b在出生的时候就能拿到a中的变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7sO5tJc-1622274262869)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527110844844.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPbVb1e5-1622274262870)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527110851632.png)]

闭包的经典应用就是防抖和节流

1、js的单例模式闭包

  <button id="loginBtn">登录</button>

    <script>
        var createLogin = function(a,b,c){
            console.log(a,b,c)
            var div = document.createElement("div");
            div.innerHTML = '我是登录的弹窗'
            div.style.display = 'none'
            document.body.appendChild(div);
            return div;
        };

        var getSingle = function(fn){
            var result;
            return function(){
                return result || (result = fn.apply(this,arguments))
            }
        }
        var create = getSingle(createLogin);
        document.getElementById("loginBtn").onclick = function(){
            var loginLay = create(1,2,3)
            loginLay.style.display = 'block'
        };
    </script>

8、为什么在调用这个函数时,代码中的b会变成全局变量

function fun(){
    	let a = b = 0;
}

因为这是一个赋值操作,从右往左赋值,由于b没有声明过,就会在全局里面创建一个b

9、哪些操作会造成内存泄露?

  • 闭包
  • 意外的全局变量
  • 被遗忘的定时器 :没有被清除
  • 脱离dom的引用 : 比如我们获取了一个dom的引用,后面这个元素被删除了,但是一直保留着对这个元素的引用

10、ES6有哪些新特性?

  • 块级作用域
  • 箭头函数
  • 模板字符串
  • 异步的Promise
  • 代理
  • 展开运算符
  • set和map

11、什么是高阶函数

  • 将函数作为参数或者返回值的函数
function highOrder(params,callback){
    return callback(params)
}

12、手写Array.prototype.map方法

原始的Map方法

        var arr = [1,2,3];
        var array = arr.map((item,index)=>{
            return item*2
        });
        console.log(array);

自己实现的

function myMap(arr,mapCallback){
    if(!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function'){
        return []
    }else {
        let result = [];
        for(var i =0;i<arr.length;i++){
            result.push(mapCallback(arr[i],i,arr));
        }
        return result;
    }
};
var res = myMap(arr,(item)=>{
    return item*2;
})
console.log(res)

13、js时间循环机制

function fun1(){
	console.log(1)
}
function fun2(){
	console.log(2)
	fun1()
	console.log(3)
}
fun2()

解释上段代码:

js逐行解析代码的时候,走到fun2()的时候,fun2插入到执行栈中,然后运行fun2函数,,输出2,解析到fun1()的时候,fun1函数插入执行栈中,此时fun1在栈顶,执行栈顶函数fun1 , 输出1,执行完毕后将fun1从栈中移除,再接着执行fun2 输出3 ,移除fun2 。栈中为空了后找微队列和宏队列中的函数

var p = new Promise(resolve =>{
    console.log(4)
    resolve(5)
})
function fun1(){
    console.log(1)
}
function fun2(){
    setTimeout(()=>{
        console.log(2)
    },0)
    fun1()
    console.log(3)
    p.then(resolve =>{
        console.log(resolve)
    })
}
fun2();

栈:

微队列:5

宏队列:2

4 1 3 5 2

14、单例模式

单例模式的理解

定义: 1 只有一个实例 2 可以全局的访问
主要解决:一个全局使用的类频繁的创建和销毁
何时使用:当你想控制实例的数目节省 系统化资源的时候
如何实现:判断系统是否已经有这个单例如果有则返回没有则创建
单例模式优点:内存中只要一个实例减少了内存的开销尤其是频繁的创建和销毁实例(比如说是首页页面的缓存)

es5实现单例模式

  • 需求 实现一个登陆框

需求实现一个登陆的弹窗

  1. 加载完成的时候已经创建好这个弹窗了 一开始是隐藏的状态弹窗出现
    缺点 : 资源的浪费

  2. 点击的时候创建

  3. 单例模式

    • 判断系统是否已经有这个单例如果有则返回没有则创建

    有一个标记 在内存中 做一 个标记–》闭包

<button id="loginBtn">登录</button>
var createLoginLayer = (function () {
    var div;
    return function () {
        if (!div) {
            div = document.createElement("div");
            div.innerHTML = '我是登录的弹窗'
            div.style.display = 'none'
            document.body.appendChild(div);
        }
        return div;
    }
})()
document.getElementById("loginBtn").onclick = function(){
    var loginLay = createLoginLayer()
    loginLay.style.display = 'block'
};

抽离出来

// 单例的职责
var getSingle = function(fn){
     var result;
     return function(){
         return result || (result = fn.apply(this,arguments))
     }
}
// 创建登录的职责
var createLoginLayer = function () {
    var div= document.createElement("div");
    div.innerHTML = '我是登录的弹窗'
    div.style.display = 'none'
    document.body.appendChild(div);
    return div;
} 
var createSingleLogin = getSingle(createLoginLayer);
document.getElementById("loginBtn").onclick = function(){
    var loginLay = createSingleLogin()
    loginLay.style.display = 'block'
};

es6实现单例模式

es5中的构造函数

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log("学前端很幸福")
}
var person1 = new Person("dd",18);
console.log(person1.name);
person1.say()

es6中的构造函数

class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    say(){
        console.log('学前端很幸福')
    }
}
let person2 = new Person("bb",12);
console.log(person2.name);
person2.say();

es6实现单例模式——利用es6的class类形成单例

class类中的静态方法只能被class类调用,不能被实例方法调用

class LyEdu{
    constructor(name,creator,products){
        this.name = name;
        this.creator= creator;
        this.products = products;
    }

    static getInstance(name,creator,products){
        if(!this.instance){
            this.instance = new LyEdu(name,creator,products)
        }
        return this.instance
    }
}
let lyCom = LyEdu.getInstance('林远教育','刘老师',['dada','es5']);
let lyComCopy = LyEdu.getInstance('林远教育copy','刘老师copy',['dadacopy','es5copy']);
console.log(lyComCopy === lyCom);//true

15、策略模式

策略模式的定义
定义一系列的算法 把他们封装起来井且他们之间可以相互替换
核心:将算法的使用和算法的实现分离开来

需求年底奖金发放

  • 绩效为S的人年
    终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。
var calculate = function(level,salary){
    if(level == 'S'){
        return salary*4
    }
    if(level == 'A'){
        return salary*3
    }
    if(level == 'B'){
        return salary*2
    }
};
console.log(calculate('S',20000))js

策略模式的实现——以下这个方法也适用于其他语言

    // 算法的实现
    var pS = function(){}
    pS.prototype.calculate = function(salary){
        return salary*4
    };
    var pA = function(){}
    pA.prototype.calculate = function(salary){
        return salary*3
    };
    var pB = function(){}
    pB.prototype.calculate = function(salary){
        return salary*2
    };

    // 算法的使用
    var Bouns = function(){
        this.salary = null; // 初始的薪水
        this.strategy = null; // 绩效等级的策略对象
    };

    Bouns.prototype.setSalary = function(salary){
        this.salary = salary;
    }
    Bouns.prototype.setStrategy = function(strategy){
        this.strategy = strategy;
    }
    Bouns.prototype.getBouns = function(salary){
        return this.strategy.calculate(this.salary)
    }
    var bouns = new Bouns()
    bouns.setSalary(10000);
    bouns.setStrategy(new pS())
    console.log(bouns.getBouns())

(js自己的语言特性) ——》简化策略模式

    var strategies = {
        "S" :function (salary) {
            return salary * 4
        },
        "A" :function (salary) {
            return salary * 3
        },
        "B" :function (salary) {
            return salary * 2
        },
    };
    var getBouns = function(level,salary){
        return strategies[level](salary)
    };
    console.log(getBouns("S",10000));

表单验证——基础版

    <form action="xxx.com" method="POST" id="registerForm">
        用户名:<input type="text" name="username" />
        密码:<input type="password" name="password"  />
        电话号码:<input type="text" name="phonenumber"  />
        <button> 提交</button>
    </form>

    <script>
        var registerForm = document.getElementById("registerForm");
        registerForm.onsubmit = function(){
            if(registerForm.username.value==''){
                alert("请输入用户名");
                return false
            }
            if(registerForm.password.value.length<6){
                alert("密码长度不能小于6位");
                return false;
            }
            if(!/^1[3|4|5|7|8][0-9]{9}$/.test(registerForm.phonenumber.value)){
                alert("电话号码格式不正确");
                return false;
            }
        }
    </script>

运用策略模式封装——优化

    // 假设我有一个验证类 Validator new Validator()
    var validateFun = function(){
        var validator = new Validator()
        // 添加验证规则
        validator.add(registerForm.username,'isNoneEmpty','用户名不能为空');
        validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');
        validator.add(registerForm.phonenumber,'isMobile','手机号格式不正确');
        // 开启验证
        var errorMsg = validator.start();
        return errorMsg
    };

    registerForm.onsubmit = function(){
        var errorMsg = validateFun();
        if(errorMsg){
            alert(errorMsg);
            return false;
        }
    }

最终版本

 		var registerForm = document.getElementById("registerForm");
 // 使用策略模式,,,封装
        // 表单验证的第一步,定义一系列算法  定义策略对象
        var strategies = {
            isNoneEmpty:function(value,errorMsg){
                if(value == ''){
                    return errorMsg
                }
            },
            minLength:function (value,length,errorMsg){
                if(value.length<6){
                    return errorMsg
                }
            },
            isMobile:function(value,errorMsg){
                if(!/^1[3|4|5|7|8][0-9]{9}$/.test(value)){
                    return errorMsg;
                }
            }
        };

        // 假设我有一个验证类 Validator new Validator()
        var validateFun = function(){
            var validator = new Validator()
            // 添加验证规则
            validator.add(registerForm.username,'isNoneEmpty','用户名不能为空');
            validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');
            validator.add(registerForm.phonenumber,'isMobile','手机号格式不正确');
            // 开启验证
            var errorMsg = validator.start();
            return errorMsg
        };

        registerForm.onsubmit = function(){
            var errorMsg = validateFun();
            if(errorMsg){
                alert(errorMsg);
                return false;
            }
        }

        // 封装策略类  构造函数  class
        var Validator = function(){
            // 保存验证规则的数组
            this.cache = [];
        }

        Validator.prototype.add = function(dom,rule,errorMsg){
            var ary = rule.split(':');
            this.cache.push(function(){
                var strategy = ary.shift() // 用户选择的验证规则
                ary.unshift(dom.value) // 把dom的value插入到头部
                ary.push(errorMsg)
                return strategies[strategy](...ary);
            })
        };

        Validator.prototype.start = function(){
            for(var i =0,vaFunc;vaFunc = this.cache[i++];){
                var msg = vaFunc();
                if(msg){
                    return msg;
                }
            }
        }

16、发布订阅者模式

17、数组扁平化处理

18、BFC的理解及作用

块级格式化上下文,它是指一个独立的块级渲染区域,只有Block-level BOX参与,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关。

只有块级盒子才参与

从一个现象开始说起

  • 一个盒子不设置height,当内容子元素都浮动时,无法撑起自身
  • 这个盒子没有形成BFC

如何创建BFC

  • float的值不是none
  • position的值不是static或者relative
  • display的值是inline-block、flex或者inline-flex
  • overflow:hidden(最好的解决方案)

BFC其他作用

  • BFC可以取消盒子margin塌陷
  • BFC可以阻止元素被浮动元素覆盖
<style>
    .father{
        float:left;
        /*position:absolute;*/ fixed也可以
        /*display:inline-block*/
        /*overflow:hidden;*/
    }
    .son{
        width:300px;
        height:300px;
        background-color:blue;
        float:left
    }
</style>
<div class="father">
    <div class="son"></div>
    <div class="son"></div>
    <div class="son"></div>
</div>

如果没哟触发BFC的话 ,father的高度就不会被撑开

下面是margin塌陷的例子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVHyo4qS-1622274262871)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527213314141.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mca61z0T-1622274262872)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527213330071.png)]

阻止浮动元素的覆盖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHIUqhHO-1622274262873)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527213801675.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdTHm1o0-1622274262873)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527213817846.png)]

触发了BFC之后的样子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TuJqPqE5-1622274262874)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210527213831041.png)]

19、call、apply、bind

call和apply会立即执行,bind不会立即执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OaNyx2LH-1622274262875)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528102323229.png)]

  • 需求 求数组里面的最大值

    let arr1 =[1,2,3,4,5,78]
    console.log(Math.max.call(null,1,2,3,4,5,78));// 78
    console.log(Math.max.call(null,arr1)); // NaN
    console.log(Math.max.apply(null,arr1)); // 78
    

应用

  • 将伪数组转化成数组——获取dom节点

    比如一个HTML中有三个div,我们要获取到这三个div,需要用到let div = document.getElementsByTagName('div'),这样得到的就是一个伪数组,我们可以用Array.prototype.slice.call(div)的方式转为数组,或者[].slice.call(div),以上代码对IE8以下的浏览器不兼容,此时就需要封装一个函数来实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4dIsMz1-1622274262876)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528103833394.png)]

  • 第二个应用就是把传的参数(类数组)给变为数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OXjuGJRs-1622274262877)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528104154966.png)]

  • 对数组的拼接

    let arr1 = [1,2,3]
    let arr2 = [4,5,6];
    console.log(arr1.concat(arr2));
    console.log(arr1);
    console.log(arr2);
    

    此时使用concat不会改变原数组,但是我们要是想要直接将arr2放入到arr1中,怎么办呢?

    可以使用for循环然后push

    也可以使用apply来实现——Array.prototype.push.apply(arr1,arr2)

  • 判断数据类型

    • typeof
    • instanceOf
    • constructor
    • Array.isArray
    let arr1 = [1,2,3];
    funcion isArray(array){
        return Object.prototype.toString.call(array) === '[object Array]'
        return Object.prototype.toString.call(array) === '[object Object]'
        return Object.prototype.toString.call(array) === '[object String]'
        return Object.prototype.toString.call(array) === '[object Null]'
    }
    

20 、 继承

原型链的继承

让子类的原型对象指向父类的实例,当子类的实例找不到对应的属性和方法时,他会沿着原型链往上找,这就是原型链继承。

    function Parent(){
        this.name = '打篮球像蔡徐坤'
    }
    Parent.prototype.getName = function(){
        return this.name;
    }

    function Child(){}
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    const child = new Child();

    console.log(child.name);
    console.log(child.getName());

原型链继承的缺点:

  • 原型指向同一个Parent实例,当我们有两个子实例化对象的时候,修改其中一个,会影响另一个
  • 无法对父类传参

要想实现super功能,其实就是解决如何对父类进行传参

构造函数继承

构造函数继承,就是在子类的构造函数当中去执行父类的构造函数,并且为其绑定子类的this。也就是说,让父类的构造函数吧成员的属性和方法挂到子类的this中去

我们可以利用构造函数继承可以解决原型链继承的第一个缺点

 function Parent(name){
        this.name = [name];
    }

    Parent.prototype.getName = function(){
        return this.name
    }
    function Child(){
        Parent.call(this,'蔡徐坤');
    }
    const child1 = new Child();
    const child2 = new Child();
    child1.name[0] = '鹿晗'
    console.log(child1.name);
    console.log(child2.name);
    console.log(child2.getName()); // 报错

缺点是: 不能继承父类原型上的方法和属性

组合式继承

组合式继承就是构造函数继承和原型链继承的结合

    function Parent(name) {
        this.name = [name];
    }

    Parent.prototype.getName = function () {
        return this.name
    }

    function Child() {
        Parent.call(this, '蔡徐坤');
    }

    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    const child1 = new Child();
    const child2 = new Child();
    child1.name[0] = '鹿晗'
    console.log(child1.name); // '鹿晗'
    console.log(child2.name); // '蔡徐坤'
    console.log(child2.getName()); // ’蔡徐坤‘

组合式继承的缺点:

用上面的代码解释——每执行一次new Child()都执行了Parent.call()和new Parent()

所以根据这个缺点,又有了寄生式组合继承

寄生式组合继承

为了解决构造函数被执行两次的问题

    function Parent(name) {
        this.name = [name];
    }

    Parent.prototype.getName = function () {
        return this.name
    }

    function Child() {
        Parent.call(this, '蔡徐坤');
    }

    Child.prototype = Parent.prototype;
    Child.prototype.constructor = Child;
    const child1 = new Child();
    const child2 = new Child();
    child1.name[0] = '鹿晗'
    console.log(child1.name); // '鹿晗'
    console.log(child2.name); // '蔡徐坤'
    console.log(child2.getName()); // ’蔡徐坤‘

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SP2QrDoJ-1622274262877)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528140335300.png)]

为了减少new Parent()的次数,我们可以直接这样。

寄生式组合继承的缺点:缺点就在Child.prototype = Parent.prototype;这里,因为我们要是想在子类的prototype里面添加一个方法,父类也会被添加上方法。也就是说我们对子类的原型的操作会影响父类原型。

怎么解决?此时我们可以加一个浅拷贝!!

    function Parent(name) {
        this.name = [name];
    }

    Parent.prototype.getName = function () {
        return this.name
    }

    function Child() {
        Parent.call(this, '蔡徐坤');
    }

    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    const child1 = new Child();
    const child2 = new Child();
    child1.name[0] = '鹿晗'
    console.log(child1.name); // '鹿晗'
    console.log(child2.name); // '蔡徐坤'
    console.log(child2.getName()); // ’蔡徐坤‘

但是我们要是在var parent = new Parent()

console.log(parent.getName())的时候,会输出undefined。

21、计算属性的应用

  1. 为什么要有计算属性?
    初衷是什么?
    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的
    逻辑会让模板过重且难以维护
    在这个模板里面就不是简单的声明式逻辑了 必须需要看一段时间才能看懂 所以对于任
    何复杂的逻辑你都应该用计算属性
    这里我们声明了一个计算属性reversedMessage. 我们提供的函数将用作
    property Vm. reversedMessage的getter 函数:
    你可以打开浏览器的控制台,自行修改例子中的vm. message
    触发set见写好的html

  2. 计算属性 vs 方法

    可以通过在表达式中调用方法来达到同样的效果:

    两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应
    式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只
    要message还没有发生改变,多次访问reversedMessage计算属性会立即返回之
    前的计算结果,而不必再次执行函数。

计算属性有get和set方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqhXl0ol-1622274262878)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528151032265.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soqecC9k-1622274262878)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528151753695.png)]

  • 计算属性具有缓存的特性,是watch不具备的
  • 计算属性一般用于同步的格式化数据的场景,依赖于其他数据,而watch可以处理一些数据派发一些异步的事件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zajzXSNR-1622274262879)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528152736706.png)]

22、es6模块化陷阱

下面代码输出什么?

// counter.js
let counter = 10;
export default counter

// index.js
import mucounter from './counter'
mycounter+=1;
console.log(mycounter)

A:10  B:11  C:Error  D:NaN

最后答案选择C

会报错,mycounter is not define

为啥呢?因为mycounter += 1 相当于 mycounter = mycounter+1; 后面那个mycounter是能找到这个变量的,但是前面的那个mycounter是找不到的。就会报错——mycounter is not define

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4tzzwHx-1622274262880)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528194628367.png)]

要是将上文代码中的mycounter+=1;改为var mycounter = mycounter+1 又会输出什么呢

会输出NaN。为什么?因为这里有变量提升和作用域。var mycounter是在mounted这个函数内,并且变量提升了会变成undefined,undefined+1等于NaN

23、手写vue plugins实现loading效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHdtKLgL-1622274262880)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210528210753233.png)]

<div id="app">
        <button @click="showLoading">显示loading</button>
    </div>
    <script>
        const LoadingComponent = Vue.extend({
            template: '<div id="loading-wrapper">{{msg}}</div>',
            props: {
                msg: {
                    type: String,
                    default:'loading......'
                }
            }
        })

        function Loading(msg) {
            const div = document.createElement('div');
            div.setAttribute('id', 'loading-wrapper');
            document.body.append(div);
            new LoadingComponent({
                props: {
                msg: {
                    type: String,
                    default:msg
                }
            }
            }).$mount("#loading-wrapper")
            return () => {
                document.body.removeChild(document.getElementById('loading-wrapper'));
            }
        }
        Vue.prototype.$loading = Loading;
        new Vue({
            el: '#app',
            methods: {
                showLoading() {
                    // 需求 hide是一个函数   然后调用该方法   loading就消失了
                    const hide = this.$loading('加载中....');
                    setInterval(() => {
                        hide();
                    }, 2000);
                }
            }
        });
    </script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值