一、面向对象
标记语言: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.原型及原型链模式
每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(“用来存储实例公用属性和方法”)
- 普通的函数
- 类(自定义类和内置类)
在prototype这个对象中,有一个天生自带的属性:constructor,这个属性存储的是当前函数本身
Fn.prototype.constructor === Fn
每一个对象数据类型的值,也有一个天生自带的属性:_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('+'));
*/
};
};