一、call、apply、bind(改变函数内部this指向)
使用场景:一般绑定方法的时候使用bind;在调用其他实例方法时候多用call/apply
1.call的作用
①.可以调用函数
var obj = { name:'alhh' } function fn(){ console.log(this) } fn.call() //window
②.改变this指向
var obj = { name:'alhh' } function fn(){ console.log(this) } fn.call(obj) //{name:'alhh'}
应用:主要作用用来实现继承
function Father(name,age){ this.name = name this.age = age } function Son(name,age,sex){ Father.call(this,name,age,sex) this.sex = '男' } var son = new Son('al','30') console.log(son) // {name: "al", age: "30", sex: "男"}
手写call原理:
Function.prototype.myCall = function (context){ if(typeof this !=='function'){ throw new TypeError('Error') } var context = context || window; //第一个参数为调用call方法的函数中的this指向 context.fn = this; //将this赋值给context的fn属性 此处this指的是调用myCall的function const args = [...arguments].slice(1) const result = context.fn(...args); //去除参数的第一个值后执行这个添加的函数 delete context.fn; //删除这个属性 return result; };
2.apply作用
①.可以调用函数
②.可以改变this的指向
var obj = { name:'alhh' } function fn(){ console.log(this) } fn.apply(obj) //{name:'alhh'}
与call不同的是,传递的参数是数组形式或者伪数组
var obj ={ name:'alhh' } function fn(args){ console.log(this) //{name:'alhh'} console.log(args) //字符串形式hello } fn.apply(obj,['hello'])
应用:求数组中的最大值
var arr =[1,33,444,55,666] //如果不需要改变this指向第一个参数可以为null var max =Math.max.apply(null,arr) 或者 //Math.max.apply(Math,arr)
console.log(max) //666
手写apply原理:和call类似主要处理参数
Function.prototype.myApply = function (context,args){ if(typeof this !=='function'){ throw new TypeError('error') } var context = context || window context.fn = this const result = context.fn(..args) delete context.fn return result }
3.bind作用
与call和apply不同的是 bind不会调用函数,返回的是由指定的this值和初始化参数改造的原函数的拷贝
相同的是可以改变this的指向;第一个参数是this指向,后面的参数和call相同 不是数组
var obj = { name:'alhh' } function fn(){ console.log(this) } var newFn =fn.bind(obj) newFn() //{name: "alhh"}
应用:如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向 此时使用this
二、new的实现
首先需要知道new的时候做了什么事情
1.创建一个新的对象
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
3.执行构造函数里面的代码(为这个新对象添加属性)
4.返回新对象
function myNew(){ //创建一个新的实例对象 var obj = new Object() //取得外部传入的构造器 var Constructor = Array.prototype.shift.call(arguments) //实现继承,实例可以访问构造器的属性 obj.__proto__ = Constructor.prototype //调用构造器,并改变this指向到实例 (参数是数组,要用apply) var res = Constructor.apply(obj,arguments) //如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象 return typeof res ==='object'?res:obj }
还可以用Object.create(),他的属性值是放在原型下面的。
function myNew(fn,...args){ var obj = Object.create(fn.prototype) var res = fn.apply(obj,...args) return typeof res ==='object'?res:obj //new里面可能会有返回值,返回值是对象 }
例子:
function Person(name){ this.name = name } var old = new Person('用new的name') var new1 = myNew(Person,'没用new的name') console.log(old,new) //{name: "用new的name"} , {name: "没用new的name"}
三.Object.create(obj,propertiesObject)
obj:创建对象的原型,表示要继承的对象 必选
propertiesObject:也是一个对象,用于对新创建的对象进行初始化
底层原理
Object.create = function(o){ var F = function(){} F.prototype = o return new F() }
继承的应用
var A = funtion(){} A.prototype.say = function(){ console.log('a') } //B的实例继承了A的属性 var B = function(){} B.prototype = Object.create(A.prototype) var b = new B() b.say() //a //重点:相对于构造函数的继承(new Object),Object.create继承实现了将A、B的原型完美分隔,双方不会互相影响,这是Object.create亮点所在
Object.create() VS new Object()
1.创建对象的方式不同
new Object()通过构造函数来创建对象,添加的属性是在自身实例下
Object.create() es6创建对象的另一种方式,可以理解为继承一个对象,添加的属性是在原型下
//new Object()方式创建 var a = {res:'apple'} var b = new Object(a) console.log(b) //{res:'apple'} b.__proto__ //{} b.res //{res:'apple'} //Object.create()方式创建 var a = {res:'apple'} var b = Object.create(a) console.log(b) //{} console.log(b.__proto__) //{res:'apple'} console.log(b.res) //{res:'apple'} //Object.create()方法创建对象时,属性是在原型下面的,也可以直接访问b.res此时这个值不是b自身的,是它通过原型链proto来访问到b的值
四.js防抖和节流
防抖和节流是js性能优化很重要的一点,它们主要针对在一些短时间内被频繁触发的事件,例如:监听输入框的输入事件来验证表单,监听页面的滚动事件来实现列表的加载,窗口的resize事件等等,这些事件都有触发频率高,间隔时间短的特点,如果这个事件的回调函数涉及到很多的计算以及DOM的重绘的话,就可能会导致卡顿,影响用户的体验,所以最好用防抖和节流函数处理一下
防抖(debounce)
原理:在事件触发一定毫秒之后再执行
function debounce(func,wait){ let timer return function(){ let self = this; let args = arguments if(timer){ clearTimeout(timer) } timer = setTimeout(function(){ func.apply(self,arguments) },wait) } } //使用 window.onscroll = debounce(function(){console.log('debounce')},1000)
节流(Throttle)
原理:保证回调函数在一个时间段内只执行一次,通过计算时间差,如果已经执行过了,清除定时器,重新开始计时,否则就执行回调函数(每隔一段时间触发一次,像水滴一样)
function throttle(fn,wait){ let preTime = Date.now() return function(){ let curTime = Date.now() if(curTime - preTime >wait){ fn.apply(this,arguments) preTime = curTime } } } //应用 window.onscroll = throttle(function(){ console.log('throttle') },1000)
五. es6的generator
主要用于异步编程,最大的特点是可以交出函数的执行权(即暂停执行)
和普通函数的区别
1:function关键字与函数名之间有一个星号
2:就是Generator函数体内部使用yield语句,可以定义不同的内部状态(内部的状态,就是函数内部的值,它在不同时候 是不一样的)
next方法可以接收参数,传入的参数,是把上一个yield语句的返回的值给覆盖了
第一个 .next()方法其实是启动器,在它之前没有yield语句,所以给第一个.next()方法去传参是没意义的
generator函数 支持for of循环