Part2:面向对象、原型链、函数、ES6基础语法

一、面向对象

标记语言:HTML5/CSS3

编程语言:编程思想

  • 面向过程 C
  • 面向对象 JAVA、PHP、C#(ASP.NET)、JavaScript…
    在这里插入图片描述

1.单例设计模式

let name='和冉';
let age=18;
let sex='美女';

let name='小璐璐';
let age=81;
let sex='看着办';

//=>把描述当前事务特征的信息进行分组归类(减少全局变量的污染)
//=>这就是JS中的单例设计模式
/*
 * beautiGirl不仅仅被叫做变量(对象名),也被称为“命名空间”
 *   单例模式:把描述事务的信息放到一个命名空间中进行归组,防止全局变量的污染
 */
let beautiGirl={
    name:'和冉',
    age:18
};
let oldMan={
    name:'小璐璐',
    age:81
};

为了让单例模式变的高大上一些,真实项目中的单例模式都这样处理

function fn(){}
let namespace = (function(){
    //创建一些方法(闭包中的私有方法)
    let fn=function(){
      	//....  
    };
    ...
    return {
        name:'xxx',
        fn:fn
    }
})();
namespace.name
namespace.fn();

例如:完成一个需要团队协作开发的案例(百度首页)

/*
 * 公共模块
 */
let utils=(function(){
    let queryElement=function(){...}
    
    return {
        //queryElement:queryElement
        queryElement
    }
})();

/*
 * 王同学负责的页卡模块
 */
let pageTabModule=(function(){
    //=>获取元素(调取其它命名空间下的方法)
    let tabBox=utils.queryElement('.tabBox');
    let show=function(){...}
    ...
    return {
        init:function(){
            //调用自己模块下的方法
            show();
        }
    }
})();
pageTabModule.init();
    
...

2.工场模式

批量化生产:把实现某个功能的代码进行封装,后期在想实现这个功能,我们直接执行函数即可

  • 低耦合:减少页面中冗余的代码
  • 高内聚:提高代码的重复使用率
function createPerson(name,age){
    let person={};
    person.name=name;
    person.age=age;
    return person;
}
let beautyGirl=createPerson('和冉',18);
let oldMan=createPerson('小璐璐',81);
beautyGirl.name
oldMan.name
...

3.构造原型模式(正统面向对象编程)

自己能够创造出自定义类和对应实例,构建起一套完整的面向对象模型

function CreatePerson(name, age) {
	this.name = name;
	this.age = age;
} 
// CreatePerson('张三', 25);//=>this:window  普通函数执行
// let person1 = new CreatePerson('和冉', 18);
/*
 * new CreatePerson()执行和普通函数执行的联系
 *   1.new这种执行方式叫做“构造函数执行模式”,此时的CreatePerson不仅仅是一个函数名,被称为“类”,而返回的结果(赋值给person1的)是一个对象,我们称之为“实例”,而函数体中出现的this都是这个实例
 */
function CreatePerson(name, age) {
	// this=>person1
	this.name = name;
	this.age = age;
	// return 100; //=>返回的还是实例
	// return {
	// 	xxx: 'xxx'
	// }; //=>如果手动RETURN的是一个基本值,对返回的实例无影响,如果手动RETURN的是一个引用类型的值,会把默认返回的实例给替换掉(所以在构造函数模式执行下,我们一般都不要手动写RETURN,防止把返回的实例给替换)
}
let person1 = new CreatePerson('和冉', 18); 

普通函数执行与构造函数执行的对比图解:
在这里插入图片描述
New执行中间发生了什么?

function Fn() {
		this.x = 100;
		this.y = 200;
	}
var f1 = new Fn();
var f2 = new Fn();

NEW执行也会把类当做普通函数执行(当然也有类执行的一面)
* 1.创建一个私有的栈内存
* 2.形参赋值 & 变量提升
* 3.浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让函数中的THIS指向这个实例对象 =>“构造函数模式中,方法中的THIS是当前类的实例”
* 4.代码执行
* 5.在我们不设置RETURN的情况下,浏览器会把创建的实例对象默认返回

【补充】:instanceof

/*
 *  instanceof:用来检测某个实例是否属于这个类
 *     实例 instanceof 类,属于返回TRUE,不属于返回FALSE
 *  [局限性]
 *     1.要求检测的实例必须是对象数据类型的,基本数据类型的实例是无法基于它检测出来的   
 */
// console.log(person1 instanceof CreatePerson);//=>TRUE
let ary = [12, 23];
console.log(ary instanceof Array); //=>TRUE
console.log(ary instanceof RegExp); //=>FALSE
console.log(ary instanceof Object); //=>TRUE 
console.log(1 instanceof Number); //=>FALSE 

/*
 * 基本数据类型在JS中的特殊性
 *   1.一定是自己所属类的实例
 *   2.但是不一定是对象数据类型的 
 */
// 字面量创建方式(也是Number类的实例,也可以调取内置的公有方法)
// let n = 10;
// console.log(n.toFixed(2));
// console.log(typeof n); //=>"number"

// 构造函数创建模式(创建出来的实例是对象类型的)
// let m = new Number("10");
// console.log(typeof m); //=>"object"
// console.log(m.toFixed(2));

构造函数执行,因为也具备普通函数执行的特点

// 构造函数执行,因为也具备普通函数执行的特点
// 1.和实例有关系的操作一定是 this.xxx=xxx ,因为this是当前类创造出来的实例
// 2.私有变量和实例没有必然的联系
function Fn(n) {
	let m = 10;
	this.total = n + m;
	this.say = function () {
		console.log(this.total);
	};
}
let f1 = new Fn(10);
let f2 = new Fn(20);
let f3 = new Fn; //=>new的时候不论是否加小括号,都相当于把Fn执行了,也创建了对应的实例,只不过不加小括号是不能传递实参的(当前案例中的形参n=undefined)
console.log(f1.m); //=>undefined
console.log(f2.n); //=>undefined
console.log(f1.total); //=>20
f2.say(); //=>this:f2(因为say执行前面有点) console.log(f2.total)=>30
console.log(f1 === f2); //=>FALSE(因为是两个不同的实例对象,也就是不同的堆地址)

在这里插入图片描述
一道开胃题:

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

let sanmao = new Dog('三毛');
sanmao.sayName();//my name is 三毛
sanmao.bark();//wangwang

阿里面试题:重写内置new

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
/*
Ctor -> constructor缩写 构造函数
params -> 后期给Ctor传递的所有的实参信息
*/
function _new(Ctor, ...params) {
    // 1.创建Ctor的一个实例对象
    // 实例.__proto__===Ctor.prototype
    let obj = {};
    obj.__proto__ = Ctor.prototype;

	//或者:let obj =  Object.create(Ctor.prototype);

    // 2.把构造函数当做普通函数执行「让方法中的THIS->实例对象」
    let result = Ctor.call(obj, ...params);

    // 3.确认方法执行的返回值「如果没有返回值或者返回的是原始值,我们让其默认返回实例对象即可...」
    if (result !== null && /^(object|function)$/.test(typeof result)) return result;
    return obj;
}
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true

【扩展】面试题:重写Object.create()

Object.create([obj]):创建一个空对象,并且让空对象.__proto__指向[obj],“把[obj]作为新实例对象的原型”。

  • [obj]可以是一个对象或者是null,但是不能是其他的值
  • Object.create(null) 创建一个不具备__proto__属性的对象「不是任何类的实例」
Object.create = function create(prototype) {
    if (prototype !== null && typeof prototype !== "object") throw new TypeError('Object prototype may only be an Object or null');
    var Proxy = function Proxy() {}
    Proxy.prototype = prototype;
    return new Proxy;
};

二、原型链

1.原型及原型链模式

  1. 每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(“用来存储实例公用属性和方法”)

    • 普通的函数
    • 类(自定义类和内置类)
  2. 在prototype这个对象中,有一个天生自带的属性:constructor,这个属性存储的是当前函数本身

    Fn.prototype.constructor === Fn

  3. 每一个对象数据类型的值,也有一个天生自带的属性:_proto_,这个属性指向“所属类的原型prototype”

    • 普通对象、数组、正则、Math、日期、类数组等等
    • 实例也是对象数据类型的值
    • 函数的原型prototype属性的值也是对象类型的
    • 函数也是对象数据类型的值

2.原型链查找机制

1.先找自己私有的属性,有则调取使用,没有继续找
2.基于__proto__找所属类原型上的方法(Fn.prototype),如果还没有则继续基于__proto__往上找…一直找到Object.prototype为止

/*
 * 类:函数数据类型
 * 实例:对象数据类型的
 */
function Fn() {
	/*
	 * NEW执行也会把类当做普通函数执行(当然也有类执行的一面)
	 *   1.创建一个私有的栈内存
	 *   2.形参赋值 & 变量提升
	 *   3.浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让函数中的THIS指向这个实例对象 =>“构造函数模式中,方法中的THIS是当前类的实例”
	 *   4.代码执行
	 *   5.在我们不设置RETURN的情况下,浏览器会把创建的实例对象默认返回 
	 */
	this.x = 100;
	this.y = 200;
	this.say = function () {}
}
Fn.prototype.eat = function () {
	console.log('吃饭睡觉打豆豆');
}
Fn.prototype.say = function () {}

var f1 = new Fn();
var f2 = new Fn();

/* //基于内置类原型扩展方法
Object.prototype.hasPubProperty = function (property) {
	//=>验证传递的属性名合法性(一般只能是数字或字符串等基本值)
	let x = ["string", "number", "boolean"],
		y = typeof property;
	if (!x.includes(y)) return false;
	//=>开始校验是否为公有的属性(方法中的THIS就是要校验的对象)
	let n = property in this,
		m = this.hasOwnProperty(property);
	return n && !m;
}
console.log(Array.prototype.hasPubProperty('push')); //=>FALSE
console.log([].hasPubProperty('push')); //=>TRUE */

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 为什么getElementById的上下文只能是document?

答:因为只有Document.prototype上才能找到getElement方法,其他原型上找不到。

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>珠峰培训</title>
</head>
<body>
	<div id="box"></div>
	<script src="3-基于内置类的原型扩展方法.js"></script>
</body>
</html>

在这里插入图片描述

2.2 hasOwnProperty
  • 检测某一个属性名是否为当前对象的私有属性

  • “in” :检测这个属性是否属于某个对象(不管是私有属性还是公有属性,只要是它的属性,结果就为TRUE)

let ary = [10,20,30];
console.log('0' in ary);  //=>TRUE 【这里0是索引】
console.log('push' in ary); //=>TRUE
console.log(ary.hasOwnProperty('0'));  //=>TRUE
console.log(ary.hasOwnProperty('push')); //=>FALSE "push"是它公有的属性不是私有的

console.log(Array.prototype.hasOwnProperty('push')); //=>TRUE 是公有还是私有属性,需要看相对谁来说的
console.log(Array.prototype.hasOwnProperty('hasOwnProperty')); //=>FALSE
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); //=>TRUE   
//自己堆中有的就是私有属性,需要基于__proto__查找的就是公有属性(__proto__在IE浏览器中(EDGE除外)给保护起来了,不让我们在代码中操作它)

在这里插入图片描述

2.3 hasPubProperty
  • 检测某个属性是否为对象的公有属性:hasPubProperty

  • 方法:是它的属性,但是不是私有的

//基于内置类原型扩展方法
Object.prototype.hasPubProperty = function (property) {
	//=>验证传递的属性名合法性(一般只能是数字或字符串等基本值)
	let x = ["string", "number", "boolean"],
		y = typeof property;
	if (!x.includes(y)) return false;
	//=>开始校验是否为公有的属性(方法中的THIS就是要校验的对象)
	let n = property in this,
		m = this.hasOwnProperty(property);
	return n && !m;
}
console.log(Array.prototype.hasPubProperty('push')); //=>FALSE
console.log([].hasPubProperty('push')); //=>TRUE
2.4 原型强化
function Fn() {
	//=>this:f1这个实例
	this.x = 100;
	this.y = 200;
	this.say = function () {
		console.log(this.x);
	}
}
Fn.prototype.say = function () {
	console.log(this.y);
}
Fn.prototype.eat = function () {
	console.log(this.x + this.y);
}
Fn.prototype.write = function () {
	this.z = 1000;
}
let f1 = new Fn;
f1.say(); //=>this:f1   =>console.log(f1.x)  =>100
f1.eat(); //=>this:f1   =>console.log(f1.x + f1.y)  =>300
f1.__proto__.say(); //=>this:f1.__proto__  =>console.log(f1.__proto__.y)  =>undefined
Fn.prototype.eat(); //=>this:Fn.prototype  =>console.log(Fn.prototype.x + Fn.prototype.y)  =>NaN
f1.write(); //=>this:f1  =>f1.z=1000  =>给f1设置一个私有的属性z=1000
Fn.prototype.write();//=>this:Fn.prototype  =>Fn.prototype.z=1000  =>给原型上设置一个属性z=1000(属性是实例的公有属性)

/*
 * 面向对象中有关私有/公有方法中的THIS问题
 *   1.方法执行,看前面是否有点,点前面是谁THIS就是谁
 *   2.把方法总的THIS进行替换 
 *   3.再基于原型链查找的方法确定结果即可
 */
2.5 基于内置类的原型扩展方法
/*
 * 基于内置类的原型扩展方法 
 *   在内置类原型上的方法,类所对应的实例可以直接调取使用,例如:实例.方法()  ary.push()
 *   如果我们也把自己写的方法放到原型上,那么当前类的实例也可以直接这样调取使用了,很方便
 * 
 * 但是也有需要注意的地方
 * 	 1.自己扩展的方法不能影响原有内置的方法(我们自己设置的方法最好加前缀:my)
 *   2.扩展方法中的THIS一般都是当前类的实例(也就是要操作的值):实例.方法()
 */
~ function () {
	/*
	 * myUnique : 实现数组去重
	 *   @params
	 *   @return
	 *      [Array] 去重后的数组
	 * by zhouxiaotian on 20190805
	 */
	function myUnique() {
		//此时没有传递要操作的ARY进来,但是方法中的THIS是当前要操作的数组:ARY.MYUNIQUE()
		let obj = {};
		for (let i = 0; i < this.length; i++) {
			let item = this[i];
			if (typeof obj[item] !== 'undefined') {
				this[i] = this[this.length - 1];
				this.length--;
				i--;
				continue;
			}
			obj[item] = item;
		}
		obj = null;
		// 保证当前方法执行完返回的结果依然是ARRAY类的一个实例
		return this;
	}
	//=>扩展到内置类的原型上
	Array.prototype.myUnique = myUnique;
}();
let ary = [12, 23, 13, 12, 23, 24, 34, 13, 23];
// ary.myUnique(); 返回去重后的数组(也是ARRAY类的实例)
// ary.sort((a, b) => a - b); 返回排序后的数组
//=>链式写法(保证返回值依然是当前类的实例 一般都会RETURN THIS)
// ary.myUnique().sort((a, b) => a - b).reverse().slice(2).push('珠峰').concat(12);//=>Uncaught TypeError: ary.myUnique(...).sort(...).reverse(...).slice(...).push(...).concat is not a function  执行完push返回的是一个数字(新增后数组的长度),不是数组了,不能在继续使用数组的方法
ary.myUnique().sort((a, b) => a - b).reverse();
console.log(ary);//[34, 24, 23, 13, 12]





/* Array.prototype.push = function () {
	console.log("哈哈哈");
}
let ary = [1, 2, 3];
ary.push(100); //=>"哈哈哈"
console.log(ary); //=>数组没变,Array.prototype.push这个操作更改了数组内置类原型上的方法*/

2.6 面向对象练习题
2.6.1 原型链机制
function Fn() {
	this.x = 100;
	this.y = 200;
	this.getX = function () {
		console.log(this.x);
	}
}
Fn.prototype.getX = function () {
	console.log(this.x);
};
Fn.prototype.getY = function () {
	console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);//false
console.log(f1.getY === f2.getY);//true
console.log(f1.__proto__.getY === Fn.prototype.getY);//true
console.log(f1.__proto__.getX === f2.getX);//false
console.log(f1.getX === Fn.prototype.getX);//false
console.log(f1.constructor);//函数Fn
console.log(Fn.prototype.__proto__.constructor);//类Object
f1.getX();//100
f1.__proto__.getX();//undefined
f2.getY();//200
Fn.prototype.getY(); //undefined


// f1.__proto__.getX();
// ->找的是公有方法
// ->this:f1.__proto__===Fn.prototype
// ->console.log(f1.__proto__.x)
// ->undefined

// f2.getY();
// ->找的是公有方法
// ->this:f2
// ->console.log(f2.y)
// ->200

// Fn.prototype.getY()
// ->找的公有的方法
// ->this:Fn.prototype
// ->console.log(Fn.prototype.y)
// ->undefined

//=>基于CONSTRUCTOR实现数据类型检测就是这样来玩的
// 但是这种方式有很大的弊端:因为用户可以去随意修改对应的CONSTRUCTOR值或者是手动给ARY增加一个私有的CONSTRUCTOR属性等
// let ary = [];
// console.log(ary.constructor === Array); //=>TRUE,证明了ary是一个数组


在这里插入图片描述

2.6.2 原型重定向
/*
 * 重构类的原型:让某个类的原型指向新的堆内存地址(重定向指向)
 *    问题:重定向后的空间中不一定有CONSTRUCTOR属性(只有浏览器默认给PROTOTYPE开辟的堆内存中才存在CONSTRUCTOR),这样导致类和原型机制不完整;所以需要我们手动再给新的原型空间设置CONSTRUCTOR属性;   
 *    问题:在重新指向之前,我们需要确保原有原型的堆内存中没有设置属性和方法,因为重定向后,原有的属性和方法就没啥用了(如果需要克隆到新的原型堆内存中,我们还需要额外的处理) =>但是内置类的原型,由于担心这样的改变会让内置的方法都消失,所以禁止了我们给内置类原型的空间重定向,例如:Array.prototype={...}这样没有用,如果想加方法Array.prototype.xxx=function(){...}可以这样处理
 */
function Fn() {
	// ...
}
Fn.prototype.xxx = function () {}
//=>批量给原型设置属性方法的时候:重构类的原型
Fn.prototype = {
	constructor: Fn,
	getA: function () {},
	getB: function () {}
}; 

 //=>批量给原型设置属性方法的时候:设置别名
let proto = Fn.prototype;
proto.getA = function () {}
proto.getB = function () {}
proto.getC = function () {}
proto.getD = function () {} 

在这里插入图片描述

2.6.3 经典面试题
function C1(name) {
	// name:undefined
	if (name) { //条件不成立
		this.name = name;
	}
}
C1.prototype.name = 'Tom';
// new C1().name; 'Tom'

function C2(name) {
	// name:undefined
	this.name = name;// this.name=undefined;
}
C2.prototype.name = 'Tom';
// new C2().name  undefined

function C3(name) {
	// name:undefined
	this.name = name || 'join';// this.name = undefined || 'join' = 'join';
}
C3.prototype.name = 'Tom';
// new C3().name 'join'

alert((new C1().name) + (new C2().name) + (new C3().name)); //=>"Tomundefinedjoin"   
2.6.4 原型练习题
function Fn(num) {
	this.x = this.y = num;
}
Fn.prototype = {
	x: 20,
	sum: function () {
		console.log(this.x + this.y);
	}
};
let f = new Fn(10);
console.log(f.sum === Fn.prototype.sum);//true
f.sum();//20
Fn.prototype.sum();//20+undefined = NaN
console.log(f.constructor); //Object

在这里插入图片描述

2.6.5 原型练习题【较难】
function Fn() {
	let a = 1;
	this.a = a;
}
Fn.prototype.say = function () {
	this.a = 2;
}
Fn.prototype = new Fn; //[重点]:new Fn创建创建之后才进行原型重定向,所以new Fn指向的是浏览器默认开辟的Fn.prototype
let f1 = new Fn;
Fn.prototype.b = function () {
	this.a = 3;
}
console.log(f1.a); //1
console.log(f1.prototype); //undefined -->[prototype是类上才有的,实例对象上没有]
console.log(f1.b); //函数 function () { this.a = 3; }
console.log(f1.hasOwnProperty('b')); //false
console.log('b' in f1); //true -->[in不管公有属性还是私有属性,找到就是true]
console.log(f1.constructor == Fn); //true

在这里插入图片描述

2.6.6 原型重定向思考题
function Fn() {
	this.x = 100;
	this.y = 100;
}
Fn.prototype.getX = function () {
	console.log(this.x);
}
let f1 = new Fn;
Fn.prototype = {
	getY: function () {
		console.log(this.y);
	}
};
let f2 = new Fn;
f1.getX();//100
console.log(f2.getX);//undefined
f2.getY();//100

在这里插入图片描述

2.6.7 手写两个方法

编写两个方法plus、minus实现如下的效果

let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15 (10+10-5)

~ function () {
	//=>x:需要加减的数字(必须是有效数字)
	function checkX(x) {
		x = Number(x);
		return isNaN(x) ? 0 : x;
	}

	function plus(x) {
		// this:我们要操作的原始值数字(this=xxx我们不能给THIS手动赋值)
		x = checkX(x);
		return this + x;
	}

	function minus(x) {
		x = checkX(x);
		return this - x;
	}

	/* 扩展到内置类的原型上 */
	Number.prototype.plus = plus;
	Number.prototype.minus = minus;
}();
let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15

三、函数

3.1 函数的三种角色
/*
 * 函数数据类型:
 *   1. 普通函数
 *   2. 类(内置类 OR 自定义类)
 * 对象数据类型:
 *   1. {}普通对象  []数组对象  /^$/正则对象  日期对象  Math数学函数对象  arguments等类数组对象  HTMLCollection/NodeList元素或者节点集合类数组对象...
 *   2. 实例也是对象数据类型的
 *   3. 类的prototype也是对象数据类型的(Function.prototype除外,它是一个匿名空函数)
 *   4. 函数也是对象 
 * 
 * ======================================
 * 函数有三种角色
 *   1. 普通函数
 *     + 形参、实参、ARGUMENTS、RETURN、箭头函数
 *     + 私有作用域(栈内存、执行上下文)
 *     + 形参赋值 & 变量提升
 *     + 作用域链      
 *     + 栈内存的释放和不释放(闭包)
 *     + ...
 *   2. 构造函数(类)
 * 	   + 类和实例
 *     + prototype 和 __proto__ 原型和原型链
 *     + instanceof
 *     + constructor
 *     + hasOwnProperty
 *     + ...
 *   3. 普通对象
 *     + 它是由键值对组成的
 *     + ...
 *   函数中的THIS也是重点需要学习的内容
 */
/* function Fn(n, m) {
	this.x = n + m;
	this.y = n - m;
	let total = n * m;
	return total;
}
Fn.prototype.say = function () {
	console.log('SAY');
}

//=>普通函数
let total = Fn(20, 10);
//=>构造函数(类和实例)
let f = new Fn(20, 10);
//=>普通对象
Fn.total = 1000; */

/* (function (global, factory) {
	//=>global:window(浏览器下运行) OR global(NODE下运行)
	//=>factory:function (window, noGlobal){...}
	"use strict";
	if (typeof module === "object" && typeof module.exports === "object") {
		//=>NODE下运行
		//...
	} else {
		//=>浏览器下运行
		factory(global);
	}
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
	//=>window:window
	//=>noGlobal:undefined
	var jQuery = function (selector, context) {
		return new jQuery.fn.init(selector, context);
	};

	var init;
	init = jQuery.fn.init = function (selector, context, root) {
		// ...
	};
	init.prototype = jQuery.fn = jQuery.prototype;

	if (!noGlobal) {
		//=>!undefined
		window.jQuery = window.$ = jQuery;
	}
}); */
/*
 * jQuery是一个构造函数:jQuery.prototype上有很多供实例操作的方法,例如css...
 *   $().css()
 * jQuery也是一个普通的对象,在它的堆空间中也存在很多的方法,例如ajax...
 *   $.ajax()
 */
// $(); //=>创建了jQuery这个类的一个实例,可以调取jQuery.prototype(jQuery.fn)上的方法

在这里插入图片描述

3.2 阿里面试题【难】
function Foo() {
	getName = function () {
		console.log(1);
	};
	return this;
}
Foo.getName = function () {
	console.log(2);
};
Foo.prototype.getName = function () {
	console.log(3);
};
var getName = function () {
	console.log(4);
};
function getName() {
	console.log(5);
}
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName(); //2  =>Foo.getName:输出2这个方法(AF2)  =>new AF2()  
new Foo().getName();//3
new new Foo().getName();//3

在这里插入图片描述

3.3 开胃题【难】
function fun(n, o) {
	console.log(o);
	return {
		fun: function (m) {
			return fun(m, n);
		}
	};
}
var c = fun(0).fun(1);//undefined  0
c.fun(2);//1
c.fun(3);//1

在这里插入图片描述

3.3 this的处理机制

每一个函数(普通函数/构造函数/内置类)都是Function这个内置类的实例,所以:函数._proto_===Function.prototype,函数可以直接调取Function原型上的方法

//Function.prototype => function anonymous(){}
/*
 * call / apply / bind  
 *    原型上提供的三个公有属性方法
 *    每一个函数都可以调用这个方法执行
 *    这些方法都是用来改变函数中的THIS指向的  
 */
function fn(){}
fn.call(); //=>fn函数基于原型链找到Function.prototype上的call方法,并且让其执行(执行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一个函数,只要是函数就能用原型上的方法,所以可以继续调用call来执行

/*
Function.prototype.call = function $1(){
    //...
}
fn.call => $1
fn.call() => $1()  this:fn
fn.call.call() => $1.call() => 继续让call执行,this:$1

实例.方法():都是找到原型上的内置方法,让内置方法先执行(只不过执行的时候做了一些事情会对实例产生改变,而这也是这些内置方法的作用),内置方法中的THIS一般都是当前操作的实例
*/
3.3.1 call方法【重点】

语法:函数.call([context],[params1],…)

函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候完成了一些功能

  • 让当前函数执行
  • 把函数中的THIS指向改为第一个传递给CALL的实参
  • 把传递给CALL其余的实参,当做参数信息传递给当前函数

如果执行CALL一个实参都没有传递,非严格模式下是让函数中的THIS指向WINDOW,严格模式下指向的是UNDEFINED

window.name = 'WINDOW';
let obj = {name: 'OBJ'};
function fn(n,m) {
	console.log(this.name);
}
fn(10,20); //=>this:window  严格下是undefined
fn.call(obj); //=>this:obj  n/m=undefined
fn.call(obj,10,20); //=>this:obj  n=10 m=20
fn.call(10,20); //=>this:10  n=20  m=undefined

fn.call(); //=>this:window  严格下是undefined
fn.call(null); //=>this:window  严格下是null(第一个参数传递的是null/undefined/不传,非严格模式下this指向window,严格模式下传递的是谁this就是谁,不传this是undefined)

/* 
//=>我们的需求是想让FN执行的时候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function  
//因为此时obj中并没有fn这个属性

-------解决办法---------

obj.fn = fn;
obj.fn(); //=>this:obj  //=>'OBJ'
delete obj.fn;
*/

基于原生的JS实现内置的call方法【重要】

~ function () {
	/*
	 * call:改变函数中的THIS指向 
	 *   @params
	 *      context 可以不传递,传递必须是引用类型值(因为后面要给它加$fn的属性)  
	 */
	function call(context) {
		//this:sum 也就是当前要操作的这个函数实例
		context = context || window;
		let args = [], //=>除第一个参数外剩余传递的信息值
			result;
		for (let i = 1; i < arguments.length; i++) {
			args.push(arguments[i]);
		}
		context.$fn = this;
		result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展开运算符,把数组中的每一项分别的展开传递给函数 //=>context.$fn(10,20)
		delete context.$fn;
		return result;
	}
	/* 扩展到内置类的原型上 */
	Function.prototype.call = call;
}();
//function fn1(){console.log(1);}
//function fn2(){console.log(2);}
//fn1.call(fn2);//1

在这里插入图片描述
阿里的面试题【难】:

~ function () {
	function call(context) {
		context = context || window;
		let args = [],
			result;
		for (let i = 1; i < arguments.length; i++) {
			args.push(arguments[i]);
		}
		context.$fn = this;
		result = context.$fn(...args);
		delete context.$fn;
		return result;
	}
	Function.prototype.call = call;
}();
// 阿里的面试题
function fn1(){console.log(1);}
function fn2(){console.log(2);}
fn1.call(fn2);//1
fn1.call.call(fn2);//2
Function.prototype.call(fn1);//没有输出结果
Function.prototype.call.call(fn1);//1

在这里插入图片描述

3.3.2 apply方法

和call方法一样,都是把函数执行,并且改变里面的this关键字的,唯一的区别就是传递给函数参数的方式不同

  • call是一个个传参
  • apply是按照数组传参
let obj={name:'OBJ'};
let fn=function(n,m){
    console.log(this.name);
}
//=>让fn方法执行,让方法中的this变为obj,并且传递10/20
fn.call(obj,10,20);
fn.apply(obj,[10,20]);

基于原生的JS实现内置的apply方法【重要】

~ function () {
    function apply(context, args=[]) { 
        context = context || window;
        var result, ary = [];
        context.$fn = this;
        result = context.$fn(...args);
        delete context.$fn;
        return result;
    }
    Function.prototype.apply = apply;
}();

function fn1(n, m) {
    console.log(1); //1
    console.log(n + m); //30
}

function fn2() {
    console.log(2);
}
fn1.apply(fn2, [10, 20]);
3.3.3 bind方法

和call/apply一样,也是用来改变函数中的this关键字的,只不过基于bind改变this,当前方法并没有被执行,类似于预先改变this

let obj={name:'OBJ'};
function fn(){
    console.log(this.name);
}
document.body.onclick=fn; //=>当事件触发,fn中的this:BODY

//=>点击BODY,让FN中的THIS指向OBJ
//document.body.οnclick=fn.call(obj); //=>基于call/apply这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给事件
document.body.onclick=function(){
    //this:BODY
    fn.call(obj);
}
document.body.onclick=fn.bind(obj); //=>bind的好处是:通过bind方法只是预先把fn中的this修改为obj,此时fn并没有执行呢,当点击事件触发才会执行fn(call/apply都是改变this的同时立即把方法执行) =>在IE6~8中不支持bind方法  预先做啥事情的思想被称为“柯理化函数”

基于原生的JS实现内置的bind方法【重要】

(function () {
	//=>this:需要改变THIS的函数
	//=>context:需要改变的THIS指向
	//=>outerArg:其余需要传递给函数的实参信息
	function myBind(context = window, ...outerArg) {
		let _this = this; 
		return function anonymous(...innerArg) {
			_this.call(context, ...outerArg.concat(innerArg));
		}
	}
	Function.prototype.myBind = myBind;
})();

let obj = {
	name: 'OBJ'
};

function fn(...arg) {
	console.log(this, arg);
	//点击输出结果:{name: "OBJ"} ,[100, 200, MouseEvent]
}
document.body.onclick = fn.myBind(obj, 100, 200);

【思考题】:如何获取数组中的最大值和最小值?

/* 获取数组中的最大值和最小值 */
let ary = [12, 24, 13, 8, 35, 15];

/* 解决方案一:先排序,第一项和最后一项就是我们需要的 */
/* ary.sort(function (a, b) {
	return a - b;
});
let min = ary[0];
let max = ary[ary.length - 1];
console.log(min, max); */

/* 解决方案二: Math.max/Math.min */
//=>Math.max/min要求我们传递的数据是一项项传递进来,获取一堆数中的最大最小,而不是获取一个数组中的最大最小
// let min = Math.min([12, 24, 13, 8, 35, 15]);
// console.log(min);//=>NaN
// let min = Math.min(12, 24, 13, 8, 35, 15);
// console.log(min); //=>8

//1.基于ES6的展开运算符
// let min = Math.min(...ary);
// console.log(min);

//2.利用apply来实现即可(this无所谓,主要是利用apply给函数传参,需要写成一个数组的特征)
// let min = Math.min.apply(Math, ary);
// console.log(min);

/* 解决方案三:假设法(假设第一个是最大的,让数组中的每一项分别和当前假设的值比较,如果比假设的值大,则把最大的值设为新的假设值,继续向后比较即可) */
let max = ary[0];
/* for (let i = 1; i < ary.length; i++) {
	let item = ary[i];
	item > max ? max = item : null;
} */
ary.forEach(item => {
	item > max ? max = item : null;
});
console.log(max); //=>35

四、ES6基础语法

4.1 let / const

ES6中新增的用来创建变量和常量的

let a = 12;
a = 13;
console.log(a); //=>13

const b = 12;
b = 13; //=>Uncaught TypeError: Assignment to constant variable. 基于CONST创建变量,变量存储的值不能被修改(常量)
console.log(b);

let 和 var 的区别

  • let 不存在变量提升(当前作用域中,不能在let声明前使用变量)
  • 同一个作用域中,let 不允许重复声明
  • let解决了typeof的一个暂时性死区问题
  • 全局作用域中,使用let声明的变量并没有给window加上对应的属性
  • let会存在块作用域(除对象以外的大括号都可被看做块级私有作用域)
4.2 箭头函数及THIS问题

ES6中新增了创建函数的方式:“箭头函数”

真实项目中是箭头函数和FUNCTION这种普通函数混合使用

箭头函数简化了创建函数的代码

//=>箭头函数的创建都是函数表达式方式(变量=函数),这种模式下,不存在变量提升,函数只能在创建完成后被执行(也就是创建的代码之后执行)
const fn=([形参])=>{
  //函数体	(return)  
};
fn([实参]);

//=>形参只有一个,小括号可以不加
const fn=n=>{};

//=>函数体中只有一句话,并且是return xxx的,可以省略大括号和return等
const fn=n=>n*10;

/*
function fn(n){
    return function(m){
        return m+(++n);
    }
}
*/
const fn=n=>m=>m+(++n);

箭头函数中没有ARGUMENTS,但是可以基于剩余运算符获取实参集合,而且ES6中是支持给形参设置默认值的

let obj = {};
let fn = (context = window, ...args) => {
	// console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined 箭头函数中没有arguments
    // ...args:剩余运算符(把除第一项外的,其它传递的实参信息都存储到ARGS这个数组集合中)
	console.log(args);
};
fn(obj, 10, 20, 30); //=>context:obj  arg:[10,20,30]
fn(); //=>context:window  arg:[]

箭头函数中没有自己的THIS,它里面用到的THIS,都是自己所处上下文中的THIS(真实项目中,一但涉及THIS问题,箭头函数慎用)

window.name = "WINDOW";
let fn = n => {
	console.log(this.name);
};
let obj = {
	name: 'OBJ',
	fn: fn
};
// FN所处的执行上下文中的THIS是WINDOW
fn(10); //=>this:window
fn.call(obj, 10); //=>this:window 不是我们预期的OBJ
document.body.onclick = fn; //=>this:window 不是我们预期的BODY
obj.fn(10); //=>this:window
let obj = {
	name: 'OBJ',
	fn: function () {
		//=>this:obj 普通函数是有自己的THIS的
		let f = () => {
			console.log(this);
		};
		f(); //=>this:obj
		return f;
	}
};
let f = obj.fn();
f(); //=>this:obj

真实项目中的一个应用

let obj = {
	name: 'OBJ',
	fn: function () {
		//=>this:obj
		//=>原本期望的需求是:1S后把OBJ中的NAME改为'珠峰'
        setTimeout(() => {
			console.log(this); //=>OBJ
			this.name = '珠峰';
		}, 1000);
        
		/* setTimeout(function () {
			console.log(this);//=>WINODOW
			this.name = '珠峰';
		}, 1000); */

		/* let _this = this;//=>把需要的THIS提前用变量存储起来
		setTimeout(function () {
			_this.name = '珠峰'; //=>需要使用的时候拿出来用即可
		}, 1000); */
	}
};
obj.fn();
4.3 解构赋值

让左侧出现和右侧值相同的结构,以此快速获取到我们需要的内容

真实项目中最常用的就是对数组和对象的解构赋值

/* 数组的解构赋值 */
// let ary = [10, 20, 30, 40, 50];
/* let n = ary[0],
	m = ary[1],
	x = ary.slice(2); */
//=>...x拓展运算符:把剩下的内容存储到X中(X是个数组),但是它只能出现在最后 
// let [n, m, ...x] = ary;
// console.log(n, m, x); //=>10 20 [30,40,50]
//=>如果没有这一项,我们可以基于等号赋值默认值
// let [n, , m, , , x = 0] = ary; 
// console.log(n, m, x); //=>10 30 0
//=>多维数组解构赋值
// let ary = [10, [20, 30, [40, 50]]];
// let [n, [, , [, m]]] = ary;
// console.log(n, m); //=>10 50

/* 对象的解构赋值 */
// let obj = {
// 	name: '王会峰',
// 	age: 79,
// 	sex: 'BOY',
// 	friends: ["唐明辉", "郭天罡", "王旭东", "郭辉"]
// };
//=>多维对象获取
// let {
// 	name,
// 	friends: [firstFriend]
// } = obj;
// console.log(name, firstFriend); //=>王会峰 唐明辉
//=>创建的变量要和对象的属性名一致(默认)
// let {
// 	name,
// 	nianling,
// 	sex
// } = obj;
// console.log(name, sex, nianling); //=>"王会峰" "BOY" undefined
//=>冒号相当于给获取的结果设置了一个别名(变量名):创建了一个叫做nianling的变量存储于了obj.age的值
// let {
// 	age: nianling
// } = obj;
// console.log(nianling); //=>79
//=>给获取的结果设置默认值(没有这个属性走的是默认值)
// let {
// 	height = "180CM"
// } = obj;
// console.log(height); //=>"180CM"

// 从服务器获取的DATA数据
let data = {
	"code": 0,
	"data": {
		"today": "2019-08-07",
		"data": [{
			"date": "2019-07-17",
			"number": "17",
			"weekday": "\u661f\u671f\u4e09"
		}, {
			"date": "2019-07-18",
			"number": "9",
			"weekday": "\u661f\u671f\u56db"
		}]
	},
	"version": "17917d337ccb7c4d34624b73efdfa0c0"
};
let {
	code,
	data: {
		today,
		data: calendarData
	}
} = data;
// console.log(code, today, calendarData);
// calendarData.forEach(item => {
// 	let {
// 		weekday,
// 		date
// 	} = item;
// 	console.log(weekday, date);
// });
4.4 “...”的作用
  • 拓展运算符(多用在解构赋值中)
  • 展开运算符(多用在传递实参中)
  • 剩余运算符(多用在接收实参中)
//=>解构赋值
let [n,...m] = [12,23,34];
//n:12
//m:[23,34]

//=>传递实参
let ary=[12,23,13,24,10,25];
let min=Math.min(...ary);
//数组克隆(浅克隆)
let cloneAry=[...ary];
//对象克隆(浅克隆)
let obj={name:'园园',age:16};
let cloneObj={...obj,sex:'girl',age:17};

//=>接收实参
let fn=(n,...arg)=>{
    //n:10
    //arg:[20,30]
};
fn(10,20,30);
4.5 class创建类
//=>传统ES3/ES5中创建类的方法
function Fn(){
    this.x=100;
};
Fn.prototype.getX=function(){
    console.log(this.x);
};
var f1 = new Fn();
f1.getX();
//也可以把它当做普通函数执行
Fn();
//还可以把Fn当做普通的对象设置键值对
Fn.queryX=function(){};
Fn.queryX();
//=>ES6中类的创建
class Fn{
    //等价于之前的构造函数体
    constructor(n,m){
        this.x=100;//给实例设置的私有属性
    }
    //这也是给实例设置的私有属性
    y=200

    //直接写的方法就是加在原型上的 === Fn.prototype.getX...
    getX(){
        console.log(this.x);
    }
    
    //前面设置static的:把当前Fn当做普通对象设置的键值对
    static queryX(){}
    static z=300
}
//也可以在外面单独这样写
Fn.prototype.y=200;
Fn.z=300;

let f = new Fn(10,20);
f.getX();
Fn.queryX();

Fn();//=>Uncaught TypeError: Class constructor Fn cannot be invoked without 'new'  =>class创建的类只能new执行,不能当做普通函数执行
4.6 ES6中的模板字符串
let year = '2019',
    month = '08',
    day = '09';
//=>"你好,小伙伴!今天是2019年08月09日,今天天气很糟糕,马上就要下大雨了,呵呵呵~~~"
let res="你好,小伙伴!今天是"+year+"年"+month+"月"+day+"日,今天天气很糟糕,马上就要下大雨了,呵呵呵~~~";

let ID="box";
let html='<ul class="list clear" id="'+ID+'">';

//=>我们真实项目中会大量进行字符串拼接操作(尤其是需要动态绑定数据:把现有的HTML代码拼接成有数据的HTML字符串),传统的ES3语法模式下字符串拼接是一个非常苦逼的任务 ┭┮﹏┭┮
let year = '2019',
    month = '08',
    day = '09';
//=>反引号(撇) TAB键上面的 (ES6模板字符串语法)
// ${} 模板字符串中书写JS表达式的方式(凡是有输出结果的都可以被称为JS表达式,一般都是一行搞定的)
let res=`你好,小伙伴!今天是${year}${month}${day}日,今天天气很糟糕,马上就要下大雨了,呵呵呵~~~`;

let ID="box";
let HTML=`<ul class="list clear" id="${ID}">
    <li><a href="javascript:;">
        <img src="img/1.jpg" alt="">
        <p></p>
        <span>¥3899</span>
    </a></li>
</ul>`;
//传统方式拼接一套HTML字符串,还需要一行行的处理,非常麻烦
4.7 THIS和面向对象的另外一种深入理解
function sum(){
   //ARGUMENTS:内置的实参集合(箭头函数中没有),不是数组是类数组(它不是数组的实例,不能直接使用数组的方法  arguments.__proto__===Object.prototype)
   let total=null;
   for(let i=0;i<arguments.length;i++){
      total+=arguments[i];
   }
   return total;
}
let total=sum(10,20,30,40);
//=>ARG:存储传递的实参信息(数组)
//=>eval:把字符串转换为JS表达式执行
let sum=(...arg)=>eval(arg.join('+'));
let total=sum(10,20,30,40);

如果ARGUMENTS是一个数组就好了~~~

~function(){
    function slice(n){
        //=>n:0 把数组克隆一份新的出来
        let newAry=[];
        for(let i=0;i<this.length;i++){
            newAry.push(this[i]);
        }
        return newAry;
    }
    Array.prototype.slice=slice;
}();
let ary=[10,20,30,40];
let newAry=ary.slice(0);//=>数组克隆
//--------------------------
function sum(){
   //=>把ARGUMENTS转换为数组:借用数组原型上的SLICE方法,只要让SLICE中的THIS指向ARGUMENTS,就相当于把ARGUMENTS转换为新数组
   //let arg=Array.prototype.slice.call(arguments,0);
   let arg=[].slice.call(arguments,0);
   //=>数组求和
   return eval(arg.join('+'));
}
let total=sum(10,20,30,40);

不仅仅是一个方法可以这样调用,很多数组的方法,类数组都能用~~

function sum(){
   let total=null;
   [].forEach.call(arguments,item=>{
      total+=item; 
   });
   return total;
}
let total=sum(10,20,30,40);
4.8 UI组件库

有结构、样式、功能的库,里面提供很多开发中常用的组件,开发中直接把组件调取过来使用即可,无需从头开发(可能需要二次开发)

  • bootstrap(经常配合JQ一起用):支持响应式布局开发(栅格系统)
  • vue:vue element / iview
  • react:antd
  • EasyUI / AmazeUI …

https://www.bootcss.com/

Bootstrap

  • v3.x
  • v4.x : https://v4.bootcss.com/
4.9 JSON格式的数据

我们基于AJAX等其它方式从服务器获取的数据结果一般都是:JSON格式或者XML格式

  • JSON格式的字符串
  • JSON格式的对象
//=>普通对象
let obj = {
    name:"珠峰"
};
//=>JSON格式对象(其实就是把属性名用双引号包起来)
obj = {
    "name":"珠峰"
};
//=>JSON格式的字符串
let str = '{"name":"珠峰"}';

把JSON格式的字符串转换为对象:window.JSON.parse

let str = '{"name":"珠峰"}';
let obj = JSON.parse(str);
//=>{ name:"珠峰" }

//=>在IE低版本浏览器中,全局对象window中没有JSON这个对象,也就没有所谓的parse等方法了
obj=eval('('+str+')'); //=>基于eval可以实现,但是为了保证转换格式正确,我们需要把字符串用小括号包起来转换

把对象转换为JSON格式字符串:JSON.stringify

let obj = {
    name:"珠峰"
};
let str = JSON.stringify(obj); //=>'{"name":"珠峰"}'
5.0 关于SORT排序的
//=>把数组中的每一项按照AGE进行升序排列
//SORT中传递一个函数,函数中有A/B
//A当前项的后一项
//B当前项
//如果当前函数返回的是一个小于零的值,让A和B的位置互换,如果返回的是大于等于零的值,位置保持不变
let ary = [{
	id: 1,
	age: 25,
	name: '张三'
}, {
	id: 2,
	age: 32,
	name: '李四'
}, {
	id: 3,
	age: 23,
	name: '张五'
}];
// 按照NAME排序 : localeCompare能够进行字符串比较
/* ary.sort((a, b) => {
	return a.name.localeCompare(b.name);
}); */
// 按照AGE升序和降序
/* ary.sort((a, b) => {
	return a.age - b.age;
});
ary.sort((a, b) => {
	return (a.age - b.age) * -1;
}); */
console.log(ary);
5.1 柯理化函数

最简单的柯理化函数:

/* 函数柯理化:预先处理的思想(利用闭包的机制) */
function fn(x){
	//=>预先在闭包中把X值存储起来
	return function(y){
		return x+y;
	}
}
fn(100)(200)

柯理化函数举例:bind方法

(function () {
	//=>this:需要改变THIS的函数
	//=>context:需要改变的THIS指向
	//=>outerArg:其余需要传递给函数的实参信息
	function myBind(context = window, ...outerArg) {
		let _this = this; 
		return function anonymous(...innerArg) {
			_this.call(context, ...outerArg.concat(innerArg));
		}
	}
	Function.prototype.myBind = myBind;
})();

let obj = {
	name: 'OBJ'
};

function fn(...arg) {
	console.log(this, arg);
}
document.body.onclick = fn.myBind(obj, 100, 200);;

//=>点击的时候FN中的this=>obj arg=>[100,200,事件对象]
// document.body.onclick = fn.bind(obj, 100, 200);
// document.body.onclick = function (ev) {
// 	fn.call(obj, 100, 200, ev);
// }
//=>执行bind方法,会返回一个匿名函数,当事件触发,匿名函数执行,我们再处理fn即可


// document.body.onclick = fn; //=>this:BODY  arg:[事件对象]
/* document.body.onclick = function (ev) {
	//=>ev事件对象:给元素的某个事件绑定方法,当事件触发会执行这个方法,并且会把当前事件的相关信息传递给这个函数“事件对象”
	console.log(ev);
} */

一道开胃小题:

/*
function fn(x,y){
    return function(z){
        return x+y+z;
    }
}
*/
let fn=(x,y)=>z=>x+y+z;
let res = fn(1,2)(3);
console.log(res); //=>6  1+2+3

练习题:请实现一个 add 函数,满足以下功能。【超难】

add(1); //1
add(1)(2); //3
add(1)(2)(3); //6
add(1)(2)(3)(4); //10
add(1)(2, 3); //6
add(1, 2)(3); //6
add(1, 2, 3); //6
function add(...A) {
	return function (...B) {
		return function (...C) {
			return eval([...A, ...B, ...C].join('+'));
		}
	}
}
add(1)(2)(3);
function currying(fn, length) {
	length = length || fn.length;
	return function (...args) {
		if (args.length >= length) {
			return fn(...args);
		}
		return currying(fn.bind(null, ...args), length - args.length);
	}
}
let add = currying((...arg) => eval(arg.join('+')), 5);
console.log(add(1, 2, 3, 4, 5));
console.log(add(1, 2)(3, 4, 5));
console.log(add(1, 2)(3, 4)(5));
console.log(add(1, 2)(3)(4)(5));
console.log(add(1)(2)(3)(4)(5));


// function $add(n1, n2, n3, n4) {
// 	return n1 + n2 + n3 + n4;
// }
// add = currying($add, 4);
// console.log(add(1, 2, 3, 4));
// console.log(add(1)(2)(3)(4)); 
//=>$add.bind(null,1).bind(null,2).bind(null,3)(4)
// function anonymous1(...innerArg){ $add.call(null,...[1,...innerArg]) }
// function anonymous2(...innerArg){ anonymous1.call(null,...[2,...innerArg])}
// function anonymous3(...innerArg){ anonymous2.call(null,...[3,...innerArg])}
// anonymous3(4) 
// 往回推
// anonymous2.call(null,3,4)
// anonymous1.call(null,2,3,4)
// $add.call(null,1,2,3,4)
// console.log(add(1, 2)(3, 4));

在这里插入图片描述

5.2 函数的防抖和节流
  • 防抖:防止“帕金森”,在频繁触发的模式下,我们只识别“一次”「识别第一次、也可以只识别最后一次」

  • 节流:降低触发的频率,它能识别“多次”「浏览器有自己的最快反应时间,例如:谷歌5-7ms,IE10-17ms,这样在我们的疯狂操作下,谷歌浏览器的频率是5ms执行一次,节流是降低这个频率,比如我们设定频率是300ms,在疯狂触发的时候,我们控制间隔300ms才让其执行一次」

==>我们自己规定频发触发的条件「例如:我们规定300MS内,只要触发两次就是频繁」
==>点击事件一般以防抖为主「但是有些需求也是节流」
==> 键盘输入事件 或者 滚动条滚动事件 都是以节流为主

防抖的实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button class="box">ddd</button>
    <script>
        /*
         * debounce:函数防抖
         *   @params
         *     func「function,required」:最后要执行的函数
         *     wait「number」:设定的频发触发的频率时间,默认值是300
         *     immediate「boolean」:设置是否是开始边界触发,默认值是false
         *   @return
         *     func执行的返回结果
         * update 2021/03/08 by zhangchuyuan
         */
        let box = document.querySelector('.box');
        box.onclick = debounce(fn, 300, true);

        function debounce(func, wait, immediate) {
            if (typeof func !== "function") throw new TypeError('func must be required and be an function!');
            if (typeof wait === "boolean") {
                immediate = wait;
                wait = 300;
            }
            if (typeof wait !== "number") wait = 300;
            var timer = null,
                result;
            return function proxy() {
                var runNow = !timer && immediate,
                    params = [].slice.call(arguments),
                    self = this;
                if (timer) clearTimeout(timer);
                timer = setTimeout(function () {
                    !timer ? result = func.apply(self, params) : null;
                    timer = null;
                }, wait);
                runNow ? result = func.apply(self, params) : null;
                return result;
            };
        }

        function fn(ev) {
            console.log('OK', ev);
        }
    </script>
</body>

</html>

节流的实现

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>珠峰在线Web高级</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            height: 500%;
            background: -webkit-linear-gradient(top left, lightblue, pink, orange);
        }
    </style>
</head>

<body>
    <script>
        /*
         * throttle:函数节流
         *   @params
         *     func「function,required」:最后要执行的函数
         *     wait「number」:设定的频发触发的频率时间,默认值是300
         *   @return
         *     func执行的返回结果
         * update 2021/03/08 by zhangchuyuan
         */
        function throttle(func, wait) {
            if (typeof func !== "function") throw new TypeError('func must be required and be an function!');
            if (typeof wait !== "number") wait = 300;
            var timer = null,
                previous = 0,
                result;
            return function proxy() {
                var now = +new Date(),
                    remaining = wait - (now - previous),
                    self = this,
                    params = [].slice.call(arguments);
                if (remaining <= 0) {
                    // 立即执行即可
                    if (timer) {
                        clearTimeout(timer);
                        timer = null;
                    }
                    result = func.apply(self, params);
                    previous = +new Date();
                } else if (!timer) {
                    // 没有达到间隔时间,而且之前也没有设置过定时器,此时我们设置定时器,等到remaining后执行一次
                    timer = setTimeout(function () {
                        if (timer) {
                            clearTimeout(timer);
                            timer = null;
                        }
                        result = func.apply(self, params);
                        previous = +new Date();
                    }, remaining);
                }
                return result;
            };
        }

        function fn(ev) {
            // console.log('OK', ev, this);
            console.log('OK');
        }
        window.onscroll = throttle(fn, 500);
    </script>
</body>

</html>
5.3 数组中的reduce方法
  • reduce也是用来实现数组的迭代的方法「可以实现每一次处理结果的累计」
  • arr.reduce([callback]) 依次迭代数组中的每一项,每迭代一次都把[callback]执行一次,并且传递三个值
    • result 上一次回调函数执行的返回结果「如果是第一次执行,获取的是数组的第一项」
    • item 依次遍历的数组每一项「从第二项开始遍历」
    • index 遍历的当前项索引
  • 把最后一次回调函数执行的返回值作为reduce的总结果
// arr.reduce([callback],[initial])
//    + result初始值是[initial]  数组从第一项开始迭代
let arr = [10, 20, 30];
let total = arr.reduce((result, item, index) => {
    console.log(result, item, index);
    // 1) 10 20 1  =>30
    // 2) 30 30 2  =>60
    return result + item;
});

重写reduce方法:

Array.prototype.reduce = function reduce(callback, initial) {
    // this->arr THIS一般是数组的实例(数组)
    if (typeof callback !== "function") throw new TypeError('callback must be a function!');
    let self = this,
        i = 0,
        len = self.length;
    if (typeof initial === "undefined") {
        initial = self[0];
        i = 1;
    }
    // 迭代数组每一项   
    for (; i < len; i++) {
        let item = self[i];
        initial = callback(initial, item, i);
    }
    return initial;
};
// Array.prototype.reduceRight = function reduceRight(callback, initial) {
//     let self = this;
//     self = self.reverse();
//     return self.reduce(callback, initial);
// };

let arr = [10, 20, 30];
let total = arr.reduce((result, item, index) => {
    return result + item;
}, 100);
console.log(total);

面试题:

//求解:
let res = fn(1, 2)(3);
console.log(res); //=>6


/*
 * ...params ES6剩余运算符「获取的结果是一个数组」
 * arguments 获取的结果是一个类数组
 */
function fn(...params) {
    return function anonymous(...args) {
        // 合并两次传递的参数 && 求和
        params = params.concat(args);

        // 数组求和
        /*
        // 方案一:命令式编程 HOW 在乎的是过程「允许我们把控过程中每一步细节  弊端:繁琐&代码多」
        let total = 0;
        for (let i = 0; i < params.length; i++) {
            total += params[i];
        }
        return total;
        */

        /*
        // 方案二:函数式编程「推荐」 WHAT 不重视过程,只在乎结果「把具体如何实现封装成为一个函数,想要实现某些需求,直接执行函数即可,对于用户来讲,函数内部如何处理不需要去管,只需要能拿到结果即可  优势:简单&减少冗余代码  弊端:只能按照既定的函数内部规则来执行,无法自己管控过程的细节」
        let total = 0;
        params.forEach(item => {
            total += item;
        });
        return total;
        */

        /*
        // 方案三:利用reduce方法
        return params.reduce((result, item) => {
            return result + item;
        }); */
        return params.reduce((result, item) => result + item);

        /*
        // 方案四:利用join、eval(投机取巧)
        // return eval(params.join('+'));
        */
    };
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值