JavaScript中的函数是一等公民
- 在JavaScript中,函数是非常重要的,并且是一等功民
- 可以作为参数传递
- 也可以作为返回值被返回
- 自己编写高阶函数(接收函数为参数或者返回另外一个函数)
- 使用内置高阶函数
//作为参数被传递
function computed(num1, num2, calcFn){
calcFn(num1,num2);
}
function add(n1,n2){
console.log(n1+n2);
};
function mul(n1,n2){
console.log(n1-n2);
};
computed(10,20, add); //30
computed(40,20, mul); //20
//作为返回值
function foo(fn){
return fn;
}
function bar(){
console.log('bar')
}
foo(bar);
数组中的函数使用
函数和方法的区别:
var obj = {
add: function(num1, num2){
return num1+num2;
}
}
function add(num1, num2){
return num1+num2;
}
第一个对象obj中add就是obj对象的方法,第二个add中的function就是作为一个独立的函数.
过滤数组得到一个新的数组,里面的元素都是偶数
var nums = [10,25,55,30,28,50];
//filter过滤
var newNums = nums.filter(item=>{
return item%2===0;
})
//map映射
var newNums = nums.map(item=>{
return item%2===0;
})
//foreach迭代
var newNums = nums.forEach(item=>{
return item%2===0;
})
// find和findIndex
let friends =[
{name: 'James', age: 35},
{name: 'kobe', age: 25},
{name: 'rose', age: 28},
{name: 'Yao', age: 33},
]
let target = friends.find(item=>{
return item.name === 'Yao';
})
console.log(target); //{name: 'Yao', age: 33}
let targetIndex = friends.findIndex(item=>{
return item.name === 'Yao';
})
console.log(targetIndex); //3
JavaScript中的闭包的定义
闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境
闭包的概念出现于60年代,最早出现闭包的程序是Scheme,JS作者又是Scheme的热衷粉,因此JavaScript中大量的设计来源于Scheme的.
MDN闭包的解释:
- 一个函数和对其周围状态的引用捆绑在一起,这样的组合叫闭包
- 闭包可以让你实现一个内层函数中访问到其外层函数的作用域.
闭包的实现
function foo(){
var name = '123';
function bar(){
console.log('bar', name);
}
return bar;
}
var fn = foo();
fn();
案例来说,foo函数执行完毕就已经被销毁,但是实际在
bar函数执行完成内部依然可以访问到变量name属性,所以综上所述闭包是包含两部分组成的:函数+可以访问的自由变量(对应代码就是bar函数和name属性),词法闭包在解析的过程中就已经确定了可以访问的自由变量,而不是定义的时候确定.
闭包与函数的最大区别是:
当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离捕捉时的上下文,它也能正常执行。
var target = '123';
function test(){
console.log('target',target);
}
狭义的闭包:JavaScript中的一个函数,如果访问到了外层作用域的变量,那么它就是一个闭包
为什么函数已经销毁了内部函数依然可以访问到它的变量呢?
foo对象表面上会被销毁,但是fn这个变量会引用bar的地址,bar的父级引用又是foo,所以foo对应的对象其实没有被销毁.
因此函数闭包会容易导致内存泄露,
闭包的内存泄露案例
function createFnArray(){
var arr = new Array(1024*1024).fill(1); //占据4M空间 4bit*1024*1024
return function (){
console.log(arr.length);
}
}
var arrFns = [];
for(let i =0; i<100; i++;){
arrFns.push(createFnArray()); //400M
}
arrFns = null; //释放内存
对于闭包中的自由变量
function foo(){
var name = "why";
var age = 18;
function bar(){
console.log(name);
}
return bar;
}
var fn = foo();
fn();
对于age属性V8引擎发现一直没有被使用,所以就会被销毁.JS引擎会对其做优化 将其临时删除掉,只能访问到name属性
this的指向
var obj = {
name: 'why',
eat(){
console.log(this.name+'吃东西.');
},
run(){
console.log(this.name+'在跑步.');
},
study(){
console.log(this.name+'在学习.')
}
}
obj.eat();
obj.run();
obj.study();
通过上面简单的实例可以看出this的指向是obj
this的全局作用域指向
console.log(this) //Node:{} 浏览器中:global window对象
大多数中this都是出现在函数中的。在Node环境中,this会被当做成一个模块进行加载,然后编译以后放置到一个函数中,执行这个函数.call,对于Node环境来说call方法传值的第一个参数是一个空对象{}
this的难点在于this并不是解析时就已经确定的,它是运行生成的.
function foo(){
console.log(this);
}
var obj = {
foo:foo
}
foo(); //window
obj.foo(); //obj
foo.apply({}) // {}
this指向什么,跟函数所处的位置是没有关系的。跟函数的调用方式有关.
this的绑定规则:
- 默认绑定
- 隐式绑定
- 显式绑定
- new绑定 默认绑定
//作为独立函数调用
function callBack(){
console.log(this);
}
callBack(); // window
function foo1(){
console.log(this);
}
function foo2(){
console.log(this);
foo1();
}
function foo3(){
console.log(this);
foo2();
}
foo3(); //window
var obj = {
foo:function(){
console.log(this);
}
}
obj.foo(); //obj
var a = obj.foo;
a(); //window
//////////////////////////////////////
function foo(){
console.log(this);
}
var obj = {
foo:foo
}
var bar = obj.foo;
bar(); //window
通过上面两个例子可以得出一个简单的结论,this的结果是什么取决于谁调用它,没有调用主题的时候指向了window,有调用主题时,指向了调用主题.
隐式绑定,是指通过某个对象进行调用的 它的调用位置是通过某个对象发起的。
var obj = {
name: 'why',
foo:function(){
console.log(this);
}
}
var fn = obj.foo;
fn(); //window
//////////////////////////////////////
var obj1 = {
foo:function(){console.log(this);}
}
var obj2 = {
bar: obj1.foo
}
obj2.bar();
obj对象的会被JavaScript引擎绑定到fn函数中的this里面,所以这种称之为隐式绑定
显式绑定,必须在调用对象的内部有一个对函数的引用,如果没有引用会提示错误
function sum(m,n){
console.log(m+n,this)
}
sum.call({},20,30); //50,{}
function sum(m,n){
console.log(m+n,this)
}
sum.apply({},[20,30]); //50,{}
//默认绑定与显式绑定发生冲突时,优先级较高的是显式绑定
function foo(){
console.log(this);
}
var newFoo = foo.bind('aaa');
newFoo(); //this=> String{"aaa"}
call和apply的区别:传参方式不同,call直接以,进行分割;apply的传参方式是以一个[]的方式.call和apply的相同之处在于可以指定this是谁;
表面上看newFoo调用主题时window,但是在调用之前就已经显示绑定到String对象上的String:{'aaa'}上面了,所以当默认绑定与显式绑定发生冲突时,优先级较高的是显式绑定.
new绑定,可以把函数当做一个类的构造函数来使用,也可以使用new关键字
function Person(){console.log(this)};
let p1 = new Person(); // this=>p1
let p2 = new Person(); //this=>p2
- 创建一个全新的对象
- 新对象会被执行prototype链接
- 新对象会绑定到函数调用的this上
通过这种构造器constructor的形式创造出来的this指向的就是创造构造器的本身.
this的其他事例
setTimeout的内部this是什么?
function realizeSetTimeout(cb, duration){
cb();
}
realizeSetTimeout(function(){
console.log(this); //window
},1000)
//模拟实现setTimeout
setTimeout(function(){
console.log(this);
},2000)
上面自己模拟实现了setTimeout中的this的实现,可以看到回调函数cb是作为独立函数被主题者调用的,所以这里的this指向的就是window,类似于内部作为独立函数执行的还有forEach,map,reduce等数组循环,可以通过修改第二个参数来更改this指向
let arr = [1,2,3];
arr.forEach(function(item){
console.log(this); //this => window
})
arr.forEach(function(item){
console.log(this); //this => {}
}, {})
DOM事件中的this
oDiv.onclick=function(){
console.log(this);
}
oDiv.addEventListener('click', function(){
console.log(this);
})
//addEventListener的模拟内部实现过程:
function addEventListener(type, cb){
cb.call(oDiv) //这里通过call的方式把this指向了oDiv
}
所以在DOM事件中的this调用指向了调用者oDiv
this绑定规则的优先级
- 默认(独立函数)绑定优先级最低
- 显式绑定优先级高于隐式绑定优先级
- new绑定高于隐式绑定
- new绑定优先级高于bind
//显式高于隐式绑定
var obj = {
foo:function(){
console.log(this);
}
}
obj.foo(); //this => obj
obj.foo.call({}); //this=>{},前面既有显式绑定又有隐式绑定,显式高于隐式
//new绑定高于隐式绑定
var obj = {
foo: function(){
console.log(this);
}
}
var f = new obj.foo(); //foo
//new绑定优先级高于bind
function foo(){
console.log(this);
}
var bar = foo.bind("aaa");
var obj = new bar();
结论: new关键字绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 独立绑定
this规则之外-忽略显示绑定
显式绑定的传值为null/undefined时:
function foo(){
console.log(this);
}
foo.call(null); //window
foo.apply(undefined); //window
使用显式绑定时候传入null或者undefined作为第一个参数时,this默认指向window,而不是undefined或者null
间接函数引用
var obj1 = {
foo:function(){
console.log(this);
}
};
var obj2 = {};
obj2.foo = obj1.foo; //this=>obj2
(obj2.foo = obj1.foo)(); //作为独立函数调用,this=> window
箭头函数的this指向
- 箭头函数不会绑定
this、arguments属性 - 箭头函数不会作为构造函数来用
//箭头函数的简写
let foo = (item)=>{
console.log(this);
}
//等价于:let foo = item => console.log(this);
//当返回结果是一个对象时,箭头函数的简写
let foo = ()=>{
return {
name: 'hello',
age: 18
}
}
//返回一个对象
let foo = ()=>({name: 'hello', age: 18});
箭头函数不适用于this绑定的4种规则,箭头函数的内部this指向外层作用域的this
let foo = ()=>{
console.log(this);
}
foo();
var obj = {foo:foo};
obj.foo(); //this => window
foo.call("abc"); //this=> window
无论对箭头函数使用何种形式的绑定,最终都是会去其上层作用域进行查找.
let obj = {
data: [],
getData:function(res){
setTimeout(function(){
this.data = res //报错,这里的函数作为作为独立函数执行的,this指向的是window。window的data属性为res
})
}
}
//修改以后:
let obj = {
data: [],
getData:function(res){
setTimeout(()=>{
this.data = res //箭头函数的this指向上层作用域 也就是obj
})
}
}
this相关面试题
面试题1
var name = "window";
var person = {
name: "person",
sayName: function(){
console.log(this.name);
}
}
function sayName(){
var sss =person.sayName;
sss(); //独立函数调用 this => window
person.sayName(); //隐式调用 this => person
(person.sayName)(); //相当于隐式调用person
(b=person.sayName)(); //赋值给b,间接函数b引用相当于独立调用window
}
sayName();
面试题2
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1(); // person1(隐式绑定)
person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)
person1.foo2(); // window(不绑定作用域,上层作用域是全局,上层作用域不是person1,是全局,这里易错)
person1.foo2.call(person2); //箭头函数不管你是怎么调用,都不绑定this,都回去上层作用域查找,this=>window
person1.foo3()(); // window(独立函数调用)
person1.foo3.call(person2)(); //取到person2作为this调用,返回是一个独立函数,最终this=> window(独立函数调用)
person1.foo3().call(person2); //显式调用, person2(最终调用返回函数式, 使用的是显示绑定)
person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2); // person1(上层找到person1)
面试题3
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2(显示高于隐式绑定)
person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)
person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
面试题4
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
//
// 上层作用域的理解:对象是没有上层作用域空间的
// var obj = {
// name: "obj",
// foo: function() {
// // 上层作用域是全局
// }
// }
//上层作用域是有上层空间的.
// function Student() {
// this.foo = function() {
// }
// }