第八章 函数
面经
为什么说JS中函数是第一公民
- 作用域 && 预解析
- 闭包
- 原型 && 原型链
- 发布订阅
- class语法糖
箭头函数
箭头函数的原型函数是什么(箭头函数没有原型对象)
与普通函数的区别
- arguments,箭头函数不绑定arguments
- this,箭头函数不绑定this
- arrow -> context this
- prototype -> new Xxx()
- arrow function没有prototype,自然不能new
作用:
- 基于不绑定this,封装HOC,达到对父环境变量 && 方法的高效使用
纯函数
- 相同输入 => 相同输出 (确定输入产生确定输出)
- 对于输出,与输入以外的其它隐藏信息无关 && IO设备产生的外部输出无关
- 无语义上可观察的函数副作用,如“触发事件”,使输出设备输出,更改输出值以外的内容等(不产生副作用)
基础
引号定义能够使用类方法是因为执行时会将其转换为对象的形式来调用方法。
函数也是对象
对象中的函数成为方法
js
函数定义
对象形式定义:
let func = new Function(参数,函数体)
func()
字面量形式定义:
funtion yz(){
}; //后边要有分号,不然有时候会出问题
yz();
函数的价值就是减少你反复造轮子。
js
赋值:
表达式赋值:
let user = {
userNmae:function(name) {
this.name = name;
},//将匿名函数赋值给属性
getUserName:function(){
return this.name;
}
}
简写表达式赋值:
let user = {
userNmae(name) {
this.name = name;
},
getUserName(){
return this.name;
}
}
全局函数
js
调用特点:
1 定义完成一个函数后,使用window来调用也可以执行。
创建完之后默认会放在window对象之中。
这样会有一个问题,就是如果定义的函数名和window下的方法相同,就会冲突,定义的函数会覆盖掉方法。
2 var定义的函数方法会压到window里,let不会
建议: 函数不要独立存放,要使用类,使用模块进行存放
匿名函数
js
定义:未声明名字的函数
function(){}
(a,b)=>a+b <==> funtion(a,b){return a+b}
js
函数提升:
function() {} 这类直接定义在script里的会提升
而使用var let定义的不会
立即执行函数&作用域冲突
js
当调用了多个js库时,并且库与库之间有相同的命名冲突,在调用时后边的依旧会覆盖掉前边的。
以往的方法是使用立即执行函数,立即执行函数的函数体内的方法在执行之后就无法调用。
解决1 :
立即执行函数
(function(window) {
function yz1() {
console.log('yz111');
}
function yz2() {
console.log('yz222');
}
window.yz = { yz1, yz2 };
})(window)
window.yz.yz1();
解决2:
使用模块
{
let yz1 = function() {
console.log('yz111');
}
let yz2 = function() {
console.log('yz222');
}
window.yz = {yz1,yz2};
}
形参与实参
js
function (a,b){
return a+b;
}
console.log(1,2);
形: a,b
实: 1,2
一般来讲 形参数量与实参数量是相对应的,不对应的话:
实参>形参 :多传了没有用,被忽略,不影响函数体。
形参>实参 :形参无数据,undefined,影响函数运行。
默认参数
js
传递默认值:
1 使用短路运算。
variable = variable || n;
2 形参定义:
function (a,b=1){}
传递了,b改变;不传递,b为默认值。
**默认参数位置放在必须传递的参数之后**
函数参数
js
普通变量可以接收任何类型,包括函数。
这样的思想,可以精进代码,优化代码结构。
优化场景:
匿名函数
***函数传参不受类型约束***
Arguments
js
arguments:传参的数量
性质是类数组
将传递的参数放在arguments里
传递的参数数量不确定,所以使用了arguments
方法:
.length() 获取了多少参数
运用在求和:
function sum() {
let total = 0;
for (let i = 0; i <= arguments.length; i++) {
total += arguments[i];
}
return total;
// console.log(arguments)
return [...arguments].reduce((a,b)=>a+b);
}
古老的语法。
适用场景: 配合点语法收集和释放参数
1 function ([...args]){}
2 console.log(func([...args])
★箭头函数
js
箭头函数是函数的简写形式。
function () {} <==> ()=>{}
():传递参数
=>:胖箭头
{}:函数表达式
与标准写法不同的是,箭头函数会直接return出来
不需要return 也不需要加分号
一个参数时可以省去括号
let yz = array.filter(item=>value<3);
!!不适用于:
递归,构造,事件处理,
★递归
js
不断重复一件事,所以需要设定一个合适的时机去退出循环。
使用场景: 重复次数不定的时候使用递归。
递归有两层顺序:
1.取值
2.回返
let num = 6
function irator(num) {
if (num == 1) {
return 1;
}
return num*irator(num - 1);
}
console.log(irator(num))
简写:
return num == 1 ? 1 : num * irator(--num);
关键因素:
1 考虑两层
2 考虑返回时机
递归传递数据的维度不变,一维依旧是一维,二维依旧是二维
js
递归练习
,{name:"english",click:325},{name:"pe",click:58}];
function show(lesson,num=100,i=1){
if(lesson.length == 100){
return lesson
}
lesson[i].click += num;
return show(lesson,num,++i);
}
console.log(show(lessons))
通过迭代+setTimeout操作++i
let i = 1;
function irator() {
return setTimeout(() => {
console.log(++i)
irator();
}, 1);
}
irator()
CallBack
在其它函数中调用的函数,没别的。
js
let num = [55, 4, 1, 4, 234, 5];
num = num.map((value, index, arr) => {
value += 100;
return value;
})
console.table(num)
简单的不写了
★this
1 全局中的this指向为window
2 在函数中,this指的是作用域内的主体,而不是方法
3 如果指向类方法或者属性,this指的是当前的对象;
在引用的时候需要加();
4 如果是普通函数,那就是全局的对象(即window)
js
场景问题:
let obj = {
name:"yang",
show:function(){
return obj.name;
}
}
console.log(obj.show());
当对象指向发生变化(名字变了)的时候,其内部的show方法也需要改变,以此类推,如果一个大项目中需要后期维护,修改的时候会极大增加维护量和修改量,所以这时候需要一个相对的指向:
return this.name;
this指的是当前对象的引用。
let demo = {
name: "yang",
paly: function(){
//第一层函数为属性
function render(){
//第二层函数为普通函数,在this指针中指向window
}
}
}
js
问题:
name = 'window 属性的name'
function User(){
this.name = name;
this.show = function(){
return this.name;
}
}
let lisi = new User();
console.log(lisi.show());
结果为:'sss';
问题在于 这一行:
this.name = name;
因为函数体内没有定义name,所以按照作用域链,会向上查找为name的变量。
因为定义在script里,所以此方法向上查找会查找到window。
最上方的 name = 'sss'; 储存在window中,所以最后打印的是window 的name。
解决:
function User(name){}//传递名称为name的参数即可
此时 this.name = name; 指向的就是构造函数User中的name。
在P125集,遗忘了再去看看,this很重要
js
(1)map+this 理解
let demo = {
name: "yang",
lists :["example1","example2","example3"],
paly: function(){
return this.lists.map(value=>console.log(value));
}
}
demo.paly();
关键点:this指向demo,所以this.lists就是使用lists。
(2)箭头函数和函数的区别 + this + map
let demo = {
name: "yang",
lists :["example1","example2","example3"],
paly: function(){
return this.lists.map(function(value){
console.log(this)
});
}
}
解决方法:在属性和属性内嵌的方法之间定义一个常量:
let demo = {
name: "yang",
lists :["example1","example2","example3"],
paly: function(){
const self = this; ***
return this.lists.map(function(value){
console.log(self) ***
});
}
}
这种方法相当于找了个中介,把this传递给中介,后续函数在使用时找到中介。
js
箭头函数和函数&this:
1 在this定义的时候,使用function会当做普通函数对待,其内部this指向都为window
2 而箭头函数的this指向则为函数体,即父级作用域的this;也可以说是上下文,是一回事
addeventlistener实际上是添加属性,所以this指向的就是主体本身
let Dom = {
site:"abcd",
bind:function(){
const button = document.querySelectorAll("div")[0];
button.addEventListener('click',()=>{
console.log(this);
})
}
}
console.log(Dom.bind());
*此时的this指向的是父级的this。
其父级function也是一个属性==>继续指向function的父级=>Dom
所以这时候this就嵌套了,
逻辑表示为:
bind属性的事件this(为属性) => bind属性的this => 对象this
*当为button.addEventListener('click',function(){})时,
因为使用标准函数添加属性的时候,其本质上为:
button.onclick = function(){}
即给button添加属性,所以function是一个属性,所以this指向当前的对象,即button。
js
同时操作当前对象和父级对象:
1 使用箭头函数
evetn=>{
event.target()当前对象
this:父级对象
}
2 使用普通函数
const self = this;
function(){
this 当前对象
self 父级对象
}
js
简单总结:
如果是:
箭头函数 this是父级的this
如果是:
普通函数 this是当前对象
++理解:
function User(){
this.name = name;
this.age = age;
}
这句话我学完this之后才理解,this.name = name这句话相当于是在User()构造函数中添加了name方法,然后将name赋值给它。
也就是说,在调用的时候就多了一个方法:
let user = new User();
user.name() 和user.age() 就可以调用 。
调用的时候不要忘记调用的是方法,要加括号()。
★构造函数
构造函数是用来生产函数的。
而构造函数的方法就是数据加工。
这里写了三种: call方法、apply方法、bind方法。
三种方法不仅限于构造函数。
因为是通过构造函数加工数据,所以数据是传入到工厂里的。
js
call方法
构造函数.call(obj,value)
1 第一个参数为this指针
2 第二参数为value值
call方法会立即执行
function Home(location){
this.location = location;
}
let newLoaction = {
where:"8"
}
Home.call(newLoaction,"604")
console.log(newLoaction)
call方法解释:
通过构造函数的call方法,可以在目标对象的基础上改变目标对象,比如该例子就是在newLocation上添加了一个value为传递的"604"的location属性。
js
apply方法
特性:第二个参数为数组。
.apply(obj,arr)
call方法:
function Factory(value,index){
this.value = value;
this.index = index;
console.log(value + index)
}
let st = {name:"yang"}
Factory.call(st,"here are value","here are index");
apply方法:
function Factory(value,index){
this.value = value;
this.index = index;
console.log(value + index)
}
let st = {name:"yang"}
Factory.apply(st,["here are value","here are index"]);
区别不大,只在最后使用构造函数改变的时候有传参的区别。
js
bind方法
特性:
1:不立即执行
2:创建新的函数==>可以在绑定的时候传参,也可以在调用的时候传参
情景:
改变函数体内的this指向的时候
具体:
document.querySelectorAll("div")[0].addEventListener('click',function(event){
alert(`${this.yang}`);
}.bind({yang:"change this director"}))
与call和apply不同的是,bind方法不会立即执行。
bind()()这样子是立即执行
let zh = function(a,b){
console.log(a+b,this.f)
return this.f + a + b;
}
let new_zh = zh.bind({f:"fff"},2,3);
new_zh()
继承
js
说白了就是复用
function Factor(){
this.get = function(param){
let res = Object.keys(param).map(k=>`${k}=${param[k]}`).join("&");
let url = `https://www.bing.com?${this.url}` + res;
console.log(url);
}
}
let dx = {
id:1,
cat:"js"
}
let de = {
id:2,
cat:"css"
}
let df = {
id:3,
cat:"html"
}
function Obj1(){
this.url = "article/lists";
Factor.call(this)
}
function Obj2(){
this.url = "lasjfklsajf";
Factor.call(this);
}
function User(){
this.url = "user/use";
Factor.call(this)
}
let o1 = new Obj1();
let us = new User();
let o2 = new Obj2();
o1.get(dx);
o2.get(de);
us.get(df);
这里 Obj1、Obj2和User都是继承了Factor()。
*案例
javascript
改变背景颜色/ bind + 继承
function ChangeColor(element) {
this.element = element
this.color = ['#2ecc71', '#2980b9', '#e67e22', '#f39c12'];
this.run = function () {
setInterval(function () {
let i = Math.floor(Math.random() * this.color.length);
this.element.style.backgroundColor = this.color[i]
}.bind(this), 1000);
}
}
let change = new ChangeColor(document.body);
change.run();
let div_change_color = new ChangeColor(document.getElementById("demo"));
div_change_color.run();