函数
函数4种调用方式
- .函数三种执行方式:普通函数 、对象方法 、构造函数
- 共同点:this的指向无法修改,是固定的(一旦修改程序报错)
- 普通函数:this指向window(
fun();
) - 对象方法:this 指向对象(
obj.fun();
) - 构造函数:this指向new创建的对象(
new Person();
)
- 普通函数:this指向window(
- 共同点:this的指向无法修改,是固定的(一旦修改程序报错)
- 函数第四种执行方式:上下文模式
- 作用: 修改函数里面的
this
- 注意点 : this只能指向引用类型(
array function object
)。如果指向基本数据类型- 基本包装类型string number boolean : 自动转成对应的对象类型 new String() new Number() new Boolean()
undefined与null
: 修改无效,this指向window- 语法: 三个语法作用一致都是修改this,只是传参方式不同
函数名.call(修改后的this,形参1,形参2)
场景:用于函数原本形参 <= 1
fn.call({name:‘张三’},10,20);函数名.apply(修改后的this,数组或伪数组)
场景:用于函数原本形参 >= 2
fn.apply({name:‘李四’},[10,20]);函数名.bind(修改后的this,形参1,形参2)
(如果这里给了形参将永远是该形参,调用赋值无效)
let newFn = fn.bind({name:‘王五’});
newFn(66,88);//this指向修改后的对象
不会立即执行函数体,而是返回一个修改this之后的新函数
场景: 用于这个函数不需要立即执行,而是过一会儿执行(事件处理函数,定时器函数)
- 作用: 修改函数里面的
伪数组 : 有数组三要素(下标,元素,长度),但是不能调用数组的API
伪数组本质是一个对象 : (属性名恰好就是下标, 但是对象原型不是指向Array.prototype,所以无法调用数字方法)
越过原型链查找访问机制
call应用(伪数组排序)
<script>
/* 伪数组排序 */
let weiArr = {
0:88,
1:50,
2:99,
3:20,
4:35,
length:5
};
//1.第一种方式: 先把伪数组转成真数组,然后调用sort
// let arr = Array.prototype.slice.call(weiArr);
// arr.sort(function(a,b){return a-b});
// console.log(arr);
//2. Array.prototype.sort.call(weiArr, function(a,b){return a-b});
//思路:越过原型链查找机制,直接调用数组原型的sort,并且把this修改为伪数组
Array.prototype.sort.call(weiArr,function(a,b){return a-b});
console.log(weiArr);
</script>
apply应用(求数组最大值–擂台思想)
<script>
/* 求数组最大值 */
let arr = [100,88,90,20,50,66];
/* 擂台思想 */
// //1.擂台思想
// let max = -Infinity;
// //2.遍历挑战者
// for(let i = 0;i<arr.length;i++){
// //3.依次和擂主PK
// if( arr[i] > max ){
// max = arr[i]
// };
// };
// console.log(max);
/* Math.max */
// let max1 = Math.max( arr[0],arr[1],arr[2],arr[3],arr[4]);
//利用apply自动遍历数组传参的特点(底层与上面代码一致,只是apply会自动遍历数组逐一传参)
let max1 = Math.max.apply(Math,arr);
console.log(max1);
</script>
bind应用
在js中,定时器中的this
默认一定是window
(要想修改定时器中的this,只能用bind)
let test = function(){
console.log('我是具名函数');
console.log(this);
};
//具名函数
setTimeout(test.bind({name:'1111'}),3000);
//匿名函数
setTimeout(function(){
console.log('我是定时器');
console.log(this);
}.bind({name:'222'}),3000);
万能检测数据类型
typeof
关键字检测数据类型 :typeof
数据- 弊端:无法检测
null
和array
这两种数据类型,返回值都是object
typeof null : 'object'
typeof []] : 'object'
- 弊端:无法检测
- 对象的
toString()
方法检测数据类型Object.prototype.toString()
: 返回固定格式字符串 [object 数据类型]Array.prototype.toString()
: 返回数组,join
拼接的字符串.底层原理是调用join()方法
- 万能检测数据类型
- 语法:
Object.prototype.toString.call(数据)
- 越过原型链,直接调用
Object.prototype
里面的toString
- 语法:
<script>
//值类型(基本数据类型)
let str = 'test';
let num = 18;
let bol = true;
let und = undefined;
let nul = null;
//引用类型(复杂数据类型)
let arr = [10,20,30];
let fn = function(){};
let obj = {name:'abc'};
console.log( obj.toString() );//[object Object]
console.log( arr.toString() );//[object Array]
console.log( Object.prototype.toString.call(str) );// [object String]
console.log( Object.prototype.toString.call(num) );// [object Number]
console.log( Object.prototype.toString.call(bol) );// [object Boolean]
console.log( Object.prototype.toString.call(und) );// [object Undefined]
console.log( Object.prototype.toString.call(nul) );// [object Null]
console.log( Object.prototype.toString.call(arr) );// [object Array]
console.log( Object.prototype.toString.call(fn) );// [object Function]
console.log( Object.prototype.toString.call(obj) );// [object Object]
// console.log( typeof str );// 'string'
// console.log( typeof num );// 'number'
// console.log( typeof bol );// 'boolean'
// console.log( typeof und );// 'undefined'
// console.log( typeof nul );// 'object'
// console.log( typeof arr );// 'object'
// console.log( typeof fn );// 'function'
// console.log( typeof obj );// 'object'
</script>
递归函数
递归函数 :
一个函数在函数体中调用自己
-
递归函数作用 :函数体代码重复执行
-
递归函数做的事和循环一样的,能用递归做的就可以用循环做。只是语法简洁性不同。
-
递归注意点 :一定要有结束条件,否则会导致死循环
-
单函数递归
function fn() {
fn();
};
fn();
- 双函数递归
function fn() {
fn2();
}
function fn2() {
fn1();
}
案例:
// 写一个函数,打印三次hello
let i = 0;
function test() {
console.log('hello');
i++
if (i <= 3) {
test();
}
}
- 递归遍历document元素
let box = document.querySelector('#box');
let arr = [];//存储所有的后代元素
function find(ele){
for(let i = 0;i<ele.children.length;i++){
console.log(ele.children[i]);
arr.push(ele.children[i]);
//继续递归调用:获取子元素的子元素
getHouDai(ele.children[i]);
}
};
find(document);
console.log(arr);
闭包(沟通全局作用域与局部作用域的一座桥梁)
- js 有两种作用域
- 全局作用域(全局变量): 在函数外面声明,可以在页面任何地方访问
- 全局变量生命周期 : 页面打开时声明, 页面关闭时销毁
- 生命周期: 从变量声明 到 变量销毁
- 局部作用域(局部变量): 在函数里面声明,只能在函数体里面使用
- 局部变量生命周期 : 执行函数体开始声明, 函数调用结束销毁
- 全局作用域(全局变量): 在函数外面声明,可以在页面任何地方访问
- 如何实现在函数外部可以访问函数里面的变量呢?
- 使用return
function fn(){
let person = {
name:'小米',
age:19
};
return person;
};
let p1 = fn();
console.log(p1);
let p2 = fn();
console.log(p2);
// p1和p2虽然里面的数据相同,但是这是两个不同的对象(地址不同)
console.log( p1 == p2);//false
- 如何实现 在函数外部访问函数里面的变量,并且保证是同一个
解决方案: 使用闭包
闭包作用: 在函数外面访问函数里面的变量
闭包语法: 语法不固定,但是主要分为三个步骤
(1)在外部函数中声明一个闭包函数
(2)在闭包函数中返回你想要访问的变量
(3)返回闭包函数
闭包本质 : 沟通全局作用域与局部作用域的一座桥梁
function outer(){
let person = {
name:'小米'
};
//(1)在外部函数的里面声明一个内部函数(闭包函数)
function closure(){
//(2)在闭包函数中返回你想要访问的变量
return person
};
//(3)返回这个闭包函数
return closure;
};
//调用外部函数 得到闭包函数
let bibao = outer();
//调用闭包函数,得到局部变量
let p1 = bibao();
let p2 = bibao();
console.log( p1,p2);
console.log( p1 == p2);//true
- 沙箱模式
沙箱模式:值得是一个独立的内存空间(局部作用域).通常是一个匿名函数自调用- 沙箱模式作用:
1. 避免全局变量污染
2. 模块化开发 : 一个功能对应一个沙箱 - 沙箱注意点 :不要在沙箱内部访问全局变量,应该使用参数传递
(1)沙箱外面的变量,代码可能会压缩出错
(2)破坏封装性
- 沙箱模式作用:
(function(w){
let a = {
eat:function(){
console.log('吃饭');
},
play:function(){
console.log('玩');
}
};
//暴露接口
w.a = a;
})(window);
class关键字作用
- 声明类函数 (相当于以前的构造函数)
- class语法作用和以前的构造函数语法作用一致,只是写法不同
- 将构造函数与原型方法 写在一个大括号中,提高代码阅读性
- calss类函数必须要使用new来调用,语法更加规范
- 语法:
class 构造函数名{
//构造函数
constructor(){
};
//原型中的方法
eat(){
};
};
对比:
//ES5继承 : 原型链
//(1)构造函数
function Person(name,age){
this.name = name;
this.age = age;
};
//(2)原型方法
Person.prototype.eat = function(){
console.log('吃饭');
};
Person.prototype.learn = function(){
console.log('学习');
};
//(3)实例对象
let p1 = new Person('ikun',30);
console.log(p1);
//ES6继承: class类函数
class Person1{
//(1)构造函数: 固定语法 constructor(){}
constructor(name,age){
this.name = name;
this.age = age;
};
//(2)原型中的方法
eat(){
console.log('吃饭');
};
learn(){
console.log('学习');
}
};
/* ES6语法的本质其实还是以前的原型语法,只是写法不同.还是可以用以前的语法给原型添加成员 */
Person1.prototype.type = '人类';
//(3)实例对象
//class类函数必须要使用new调用,否则会报错
let p2 = new Person1('班长',18);
console.log(p2);
extends关键字 : 用于继承
- 底层原理: 替换原型
Student.prototype.__proto__ = Person.prototype
s1.__proto__.__proto__ === Person.prototype
/* 父对象 */
class Person{
//构造函数
constructor(name,age){
this.name = name;
this.age = age;
};
//原型方法
eat(){
console.log('人类要吃饭');
}
};
Person.prototype.type = '人类';
let p1 = new Person('xiuer',18);
/* 子对象 继承与父对象
extends关键字底层原理: Student.prototype.__proto__ = Person.prototype
*/
class Student extends Person{
//构造函数
// constructor(name,age,score){
// this.name = name;
// this.age = age;
// this.score = score
// };
//原型方法
work(){
console.log('好好学习天天向上');
}
};
let s1 = new Student('班长',20);
console.log(s1);
console.log(s1.type);//人类
s1.eat();//吃饭
super关键字:在子类函数中调用父类的方法
- 如果重写了子类的构造函数construct,必须要调用父类的方法super()
/* 父对象 */
class Person{
//构造函数
constructor(name,age){
this.name = name;
this.age = age;
};
//原型方法
eat(){
console.log('人类要吃饭');
}
};
Person.prototype.type = '人类';
let p1 = new Person('ikun',30);
/* 子对象 继承与父对象
extends关键字底层原理: Student.prototype.__proto__ = Person.prototype
*/
class Student extends Person{
//构造函数
constructor(name,age,score){
//在子类构造函数中 去调用父类的构造函数
/* 细节:如果重写了子类的构造函数construc,必须要调用父类的方法super()
*/
super(name,age);
this.score = score
};
//原型方法
work(){
/* 调用父类的方法 */
//super.eat()底层原理: Person.prototype.eat();
super.eat();
console.log('好好学习天天向上');
}
};
let s1 = new Student('班长',20,99);
console.log(s1);
s1.work();
// console.log(s1.type);//人类
// s1.eat();//吃饭