前言
这段时间突然发现JS原生好多东西都忘记了,但有些东西确实很重要,所以又重新再梳理一次。主要有函数的3种定义方法,ES5函数this指向,call与appl用法,JS常见的4种设计模式,原型链,原型链和继承的方式(ES5和ES6)
1.函数的3种定义方法
1.1 函数声明
//ES5
function getSum(){}
function (){}//匿名函数
//ES6
()=>{}//如果{}内容只有一行{}和return关键字可省,
1.2 函数表达式(函数字面量)
//ES5
var sum=function(){}
//ES6
let sum=()=>{}//如果{}内容只有一行{}和return关键字可省,
1.3 构造函数
var sum=new GetSum(num1,num2)
1.4 三种方法的对比
1.函数声明有预解析,而且函数声明的优先级高于变量;
2.使用Function构造函数定义函数的方式是一个函数表达式,这种方式会导致解析两次代码,影响性能。第一次解析常规的JavaScript代码,第二次解析传入构造函数的字符串
2.ES5中函数的4种调用
在ES5中函数内容的this指向和调用方法有关
2.1 函数调用模式
包括函数名()和匿名函数调用,this指向window
function getSum() {
console.log(this) //window
}
getSum()
(function() {
console.log(this) //window
})()
var getSum=function() {
console.log(this) //window
}
getSum()
2.2 方法调用
对象.方法名(),this指向对象
var objList = {
name: 'methods',
getSum: function() {
console.log(this) //objList对象
}
}
objList.getSum()
2.3 构造器调用
new 构造函数名(),this指向构造函数
function Person() {
console.log(this); //指向构造函数Person
}
var personOne = new Person();
2.4 间接调用
利用call和apply来实现,this就是call和apply对应的第一个参数,如果不传值或者第一个值为null,undefined时this指向window
function foo() {
console.log(this);
}
foo.apply('我是apply改变的this值');//我是apply改变的this值
foo.call('我是call改变的this值');//我是call改变的this值
3.ES6中函数的调用
箭头函数不可以当作构造函数使用,也就是不能用new命令实例化一个对象,否则会抛出一个错误
箭头函数的this是和定义时有关和调用无关
调用就是函数调用模式
(() => {
console.log(this)//window
})()
let arrowFun = () => {
console.log(this)//window
}
arrowFun()
let arrowObj = {
arrFun: function() {
(() => {
console.log(this)//arrowObj
})()
}
}
arrowObj.arrFun();
4.call,apply和bind
1.IE5之前不支持call和apply,bind是ES5出来的;
2.call和apply可以调用函数,改变this,实现继承和借用别的对象的方法;
4.1 call和apply定义
调用方法,用一个对象替换掉另一个对象(this)
对象.call(新this对象,实参1,实参2,实参3.....)
对象.apply(新this对象,[实参1,实参2,实参3.....])
4.2 call和apply用法
1.间接调用函数,改变作用域的this值
2.劫持其他对象的方法
var foo = {
name:"张三",
logName:function(){
console.log(this.name);
}
}
var bar={
name:"李四"
};
foo.logName.call(bar);//李四
实质是call改变了foo的this指向为bar,并调用该函数
3.两个函数实现继承
function Animal(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
var cat = new Cat("Black Cat");
cat.showName(); //Black Cat
4.为类数组(arguments和nodeList)添加数组方法push,pop
(function(){
Array.prototype.push.call(arguments,'王五');
console.log(arguments);//['张三','李四','王五']
})('张三','李四')
5.合并数组
let arr1=[1,2,3];
let arr2=[4,5,6];
Array.prototype.push.apply(arr1,arr2); //将arr2合并到了arr1中
6.求数组最大值
Math.max.apply(null,arr)
7.判断字符类型
Object.prototype.toString.call({})
4.3 bind
bind是function的一个函数扩展方法,bind以后代码重新绑定了func内部的this指向,不会调用方法,不兼容IE8
var name = '李四'
var foo = {
name: "张三",
logName: function(age) {
console.log(this.name, age);
}
}
var fooNew = foo.logName;
var fooNewBind = foo.logName.bind(foo);
fooNew(10)//李四,10
fooNewBind(11)//张三,11 因为bind改变了fooNewBind里面的this指向
4.4 call,apply和bind原生实现
call实现:
Function.prototype.newCall = function(context, ...parameter) {
context.fn = this;
context.fn(...parameter);
delete context.fn;
}
let person = {
name: 'Abiel'
}
function sayHi(age,sex) {
console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男
apply实现:
Function.prototype.newApply = function(context, parameter) {
if (typeof context === 'object') {
context = context || window
} else {
context = Object.create(null)
}
let fn = Symbol()
context[fn] = this
context[fn](parameter);
delete context[fn]
}
bind实现:
Function.prototype.bind = function (context,...innerArgs) {
var me = this
return function (...finnalyArgs) {
return me.call(context,...innerArgs,...finnalyArgs)
}
}
let person = {
name: 'Abiel'
}
function sayHi(age,sex) {
console.log(this.name, age, sex);
}
let personSayHi = sayHi.bind(person, 25)
personSayHi('男')
4.5 三者异同
同:都是改变this指向,都可接收参数
异:bind和call是接收单个参数,apply是接收数组
5.函数的节流和防抖
类型 | 概念 | 应用 |
---|---|---|
节流 | 某个时间段内,只执行一次 | 滚动条,resize事件一段时间触发一次 |
防抖 | 处理函数截止后一段时间依次执行 | scroll,resize事件触发完后一段时间触发 |
节流:
5.1 节流
let throttle = function(func, delay) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(function() {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
};
function handle() {
console.log(Math.random());
}
window.addEventListener("scroll", throttle(handle, 1000)); //事件处理函数
5.2 防抖
function debounce(fn, wait) {
var timeout = null;
return function() {
if (timeout !== null) clearTimeout(timeout);//如果多次触发将上次记录延迟清除掉
timeout = setTimeout(function() {
fn.apply(this, arguments);
timer = null;
}, wait);
};
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener("onscroll", debounce(handle, 1000));
6.原型链
6.1 定义
对象继承属性的一个链条
6.2构造函数,实例与原型对象的关系
var Person = function (name) { this.name = name; }//person是构造函数
var o3personTwo = new Person('personTwo')//personTwo是实例
原型对象都有一个默认的constructor属性指向构造函数
6.3 创建实例的方法
1.字面量
let obj={'name':'张三'}
2.Object构造函数创建
let Obj=new Object()
Obj.name='张三'
3.使用工厂模式创建对象
function createPerson(name){
var o = new Object();
o.name = name;
};
return o;
}
var person1 = createPerson('张三');
4.使用构造函数创建对象
function Person(name){
this.name = name;
}
var person1 = new Person('张三');
6.4 new运算符
1.创了一个新对象;
2.this指向构造函数;
3.构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象
4.手动封装一个new运算符
var new2 = function (func) {
var o = Object.create(func.prototype); //创建对象
var k = func.call(o); //改变this指向,把结果付给k
if (typeof k === 'object') { //判断k的类型是不是对象
return k; //是,返回k
} else {
return o; //不是返回返回构造函数的执行结果
}
}
更多详情:详谈JavaScript原型链
6.5 对象的原型链
7.继承的方式
JS是一门弱类型动态语言,封装和继承是他的两大特性
7.1原型链继承
将父类的实例作为子类的原型
1.代码实现
定义父类:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
子类:
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish'));//cat正在吃:fish undefined
console.log(cat.sleep());//cat正在睡觉! undefined
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
2.优缺点
简单易于实现,但是要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,无法实现多继承
7.2 构造继承
实质是利用call来改变Cat中的this指向
1.代码实现
子类:
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
2.优缺点
可以实现多继承,不能继承原型属性/方法
7.3 实例继承
为父类实例添加新特性,作为子类实例返回
1.代码实现
子类
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
2.优缺点
不限制调用方式,但不能实现多继承
7.4 拷贝继承
将父类的属性和方法拷贝一份到子类中
1.子类:
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
2.优缺点
支持多继承,但是效率低占用内存
7.5 组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
1.子类:
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
7.6 寄生组合继承
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
7.7 ES6的extends继承
ES6 的继承机制是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this,链接描述
//父类
class Person {
//constructor是构造方法
constructor(skin, language) {
this.skin = skin;
this.language = language;
}
say() {
console.log('我是父类')
}
}
//子类
class Chinese extends Person {
constructor(skin, language, positon) {
//console.log(this);//报错
super(skin, language);
//super();相当于父类的构造函数
//console.log(this);调用super后得到了this,不报错,this指向子类,相当于调用了父类.prototype.constructor.call(this)
this.positon = positon;
}
aboutMe() {
console.log(`${this.skin} ${this.language} ${this.positon}`);
}
}
//调用只能通过new的方法得到实例,再调用里面的方法
let obj = new Chinese('红色', '中文', '香港');
obj.aboutMe();
obj.say();
更多详情请戳:JS继承的实现方式
8.高阶函数
8.1定义
函数的参数是函数或返回函数
8.2 常见的高阶函数
map,reduce,filter,sort
8.3 柯里化
1.定义:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
fn(a,b,c,d)=>fn(a)(b)(c)(d)
2.代码实现:
function curry(fn) {
let arg = []; //用于收集参数
//做一个闭包
return function () {
//每执行一次收集一次参数,为什么用concat是因为有时候后是多个参数(2,3)
arg = [...arg,...arguments];
//直到参数收集完成执行fn
// 我们需要知道什么时候收集完了,条件就是curry参数fn的参数个数 fn.length
//如果收集的参数个数大于等于fn的参数个数执行fn,如果没有递归执行
// fn.length是形参个数
if (arg.length >= fn.length) {
return fn(...arg)
}
// 参数没有收集完我们需要继续收集,递归,callee指向arguments的函数
return arguments.callee
}
}
function volume(l, h, w) {
return l + h + w
}
const hCy = curry(volume)
console.log(hCy(100)(200)(900)) // 1200
8.4 反柯里化
1.定义:
obj.func(arg1, arg2)=>func(obj, arg1, arg2)
2.代码实现:
Function.prototype.uncurrying = function() {
var that = this;
return function() {
return Function.prototype.call.apply(that, arguments);
}
};
function sayHi () {
return "Hello " + this.value +" "+[].slice.call(arguments);
}
let sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:'world'},"hahaha"));
8.5偏函数
1.定义:指定部分参数来返回一个新的定制函数的形式
2.例子:
function foo(a, b, c) {
return a + b + c;
}
function func(a, b) {
return foo(a,b,8);
}
参考文献:
https://www.cnblogs.com/tugen...
https://www.cnblogs.com/humin...
https://www.cnblogs.com/cheng...
https://www.cnblogs.com/cheng...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。