函数
闭包
//闭包的形式
function fn(){
function fn1(){
console.log("aa")
}
return fn1;
}
var f=fn();// --> fn()();
f();
`闭包的作用`
防止变量污染,创建一个作用域内的私有变量
`三种遍历的区别`
全局变量
保留,可以继续对上次的结果进行修改,累计的效果,全局暴露
局部变量
不保留,运行完函数会被销毁,下次重新开始,不会累计不会暴露在外
私有变量
不暴露在外,并且可以下次累计修改,不会因为函数执行完成后被销毁
`注`
函数内的局部函数中,有权访问父级以上作用域中的局部变量和全局变量
子级作用域中的局部变量无法被父级访问
//当闭包情况下,父级作用域中局部变量,如果在子级函数作用域中被调用,该局部变量不会被销毁(这样变量将会被留驻内存中无法被销毁掉,这叫做内存泄漏)
`产生闭包`
函数套函数,外部函数执行完成后返回内部的函数,并且内部函数有权访问外部函数中局部变量
如果当前这个函数内还有函数,并且在这个作用域内部函数中仍然使用了这个局部变量 这个局部变量在当前函数执行完成后不会被销毁(形成私有变量,副作用是造成内存泄漏)
var a=1;
function fn(){
var b=2;// 私有变量 不会被销毁
function fn1(){
var c=3;
a++;
b++;
c++;
// a是全局变量
// b是私有变量
// c是局部变量 每一次进来都是3
}
return fn1;
}
私有变量的优势
1.防止变量重名覆盖,防止变量污染
(function () {
var a = 1;
function fn() {
console.log(a);
}
fn();
})();
//下一行以括号开头 要加;
(function () {
var a = 10;
function fn() {
a++;
console.log(a);
}
fn();
})()
2.处理this的指向问题
`函数的原型链`
//在函数的原型下添加一个play方法
Function.prototype.play=function(){
console.log("aa")
console.log(this)//fn
}
function fn(){
}
fn.play();
console.log(fn)
`实例化对象的原型链中添加方法`
//如果要给一个实例化对象的原型链中添加一个方法,只需要给它的类的prototype中添加这个方法
Object.prototype.play = function () {
}
var o = { a: 1 }
console.log(o)//此时对象o下的prototype 就会有一个play方法
`注意点!!! 谁调用添加的方法 则在函数中this就指向谁`
重构call
Function.prototype.call_1 = function (thisArg, ...arg) {
//thisArg是下面的对象o
//arg 参数数组
// 将函数放在thisArg这个对象的方法中,当执行这个对象的方法时,里面的this就会执行thisArg这个对象
//这里的this指向函数fn
var obj = { f: this };
Object.setPrototypeOf(thisArg, obj);//给thisArg设置原型链为obj
//此时thisArg中的prototype下有了fn(a, b)函数 thisArg可以进行调用
//相当于在对象o中调用属性f对应的函数
thisArg.f(...arg)
}
function fn(a, b) {
console.log(this, a, b);
}
var o = { a: 1 }
fn.call_1(o, 1, 2)
重构apply
//简单方法
Function.prototype.apply_1 = function (thisArg, argArray) {
var obj = { f: this };
Object.setPrototypeOf(thisArg, obj);
thisArg.f(...argArray);
}
function fn(a, b) {
console.log(this, a, b)
}
var o = { a: 1 }
fn.apply_1(o, [1, 2])
//复杂写法
Function.prototype.apply_1 = function (thisArg, argArray) {
//thisArg是下面传入的对象o
//argArray 是数组参数
//这里的this指向函数fn
var obj = { f: this };
if (typeof thisArg !== "object") {
if (thisArg === undefined) {
thisArg = window;
} else {
// 如果传入的不是对象就创建一个他原型的对象
thisArg = new thisArg.constructor(thisArg);
console.log(thisArg);
}
} else if (thisArg === null) {
thisArg = window
}
if (thisArg !== window) Object.setPrototypeOf(thisArg, obj);//给thisArg设置原型链为obj
else thisArg.f = this;
//谁调用f this就指向谁
thisArg.f(...argArray);
}
function fn(a, b) {
console.log(this, a, b)
}
var o = { a: 1 };
fn.apply_1(o, [1, 2])//this指向对象o
fn.apply_1(undefined, [1, 2])//this指向对象undefined
fn.apply_1(null, [1, 2])//this指向对象null
重构bind
Function.prototype.bind_1 = function (thisArg, ...arg) {
//this指向的是fn
//下面闭包中如果调用this,this将会被指向window,因为闭包中函数,在任何时候调用this都会被执行window
//所以在这里先把this存储局部变量f,就变成下面闭包的私有变量f
var f = this;
//thisArg 就是调用bind_1传入的第一个参数,目的是执行fn时改变其内部的this指向为thisArg(当前是obj)
//...arg 是指向bind_1时传入的剩余参数
return function (...arg1) {
f.apply(thisArg, [...arg, ...arg1])
}
}
function fn(a, b, c, d) {
//this指向对象obj
console.log(this, a, b, c, d)
}
var obj = { a: 1 };
var fn1 = fn.bind_1(obj, 1, 2, 3, 4)
fn1(3, 4)
3.柯里化 currying
//柯里化是将接受多个参数的函数转换成一系列只接受单个参数的函数的过程。
//函数每次传入参数,都会保存起来,当函数执行不传入参数时,将把所有的参数处理一个结果返回(可以将值存储在函数的局部变量中,等到想触发时再触发)
function currying() {
var list = [];
return function (...arg) {
if (arg.length > 0) {
list.push(...arg);
} else {
return list.reduce((v, t) => v + t);
}
}
}
var getSum = currying();
getSum(1, 2, 3)
getSum(4, 5, 6);
var sum = getSum();
console.log(sum)
function currying(fn) {
var list = [];
return function f(...arg) {
if (arg.length > 0) {
list.push(...arg);
return f;
} else {
return fn(...list)
}
}
}
var getSum = currying(function (...arg) {
return arg.reduce((v, t) => v + t);
})
//注:最后函数的执行一定不要传入参数
//方式一
getSum(1, 2, 3);
getSum(4, 5, 6)
var sum = getSum();
console.log(sum)
//方式二
var sum = getSum(1, 2, 3)(4, 5, 6)();
console.log(sum)
`反柯里化`
//反柯里化是将柯里化函数转换成接受多个参数的函数的过程。
//反柯里化函数的返回值是一个函数,该函数接受一个对象作为参数,并调用该对象的原本方法并传递参数。
Function.prototype.uncurrying = function () {
var self = this;
return function () {
return Function.prototype.call.apply(self, arguments);
/*
self.call(arguments[0],arguments[1],arguments[2]...)
arguments[0] 设置为self这个函数的this,后面arguments[1]。。。则作为self的参数
self就是slice方法,slice中this从数组变为了list,arguments[1]就是2 arguments[2] 4
*/
}
}
var list = document.querySelectorAll("div");
var slice = [].slice.uncurrying();
var arr = slice(list, 2, 4)
console.log(arr)
原型和继承
`定义`
1.任何一个函数(且仅有函数)都有一个属性对象叫做原型 prototype
2.prototype属性值是一个对象,对象下默认有一个属性constructor,并且这个constructor属性值指向当前函数
function fn() {
console.log("aaa")
}
//打印出fn的所有属性和属性值.
console.dir(fn)
`函数的执行`
1.fn() 直接执行函数 函数可以使用return返回一个任何内容
函数的prototype属性不会对这个执行过程有任何影响
2.new fn() 实例化函数 函数不能使用return返回任何内容,因为new fn()会自动返回一个实例化对象,所以不能接收return的结果
"实例化以后对象共有一个原型链",也就是这个fn的prototype对象(//函数中prototype属性自动变为实例化对象的原型链)
用于实例化这个fn函数叫做构造函数,'实例化实际上是通过原型创建了一个新对象"
'因为所有的构造函数的标识都是constructor,所以所有的实例化对象都可以通过 对象.constructor得到这个对象的构造函数,也就是这个对象的类别'
`给原型添加属性或者方法`
//给Box类的原型添加属性和方法
function Box() {}
// 方法一
Box.prototype.a=1;
Box.prototype.play=function(){
console.log("aa");
}
// 方法二
Object.assign(Box.prototype,{})
// 方法三
Object.defineProperties(Box.prototype, {
a: { writable: true, value: 1 },
play: {
value: function () {
console.log("aaa");
},
},
});
Object.defineProperty(Box1.prototype,"play",{
value(){
console.log("play");
}
})
new
`作用`
1.根据传入的函数的原型创建一个对象
2.把对象作为这个函数this指向函数,并且执行函数
3.返回这个创建的对象
//重构new
function news(fn, ...arg) {
var o = Object.create(fn.prototype);
fn.apply(o, arg);
return o;
}
function fn(a,b){
this.a=a;
this.b=b;
console.log(a,b);
}
fn.prototype.c=3;
fn.prototype.d=4;
var o1=news(fn,1,2);
console.log(o1)
小案例
//一维数组变二维数组
Array.prototype.chunk = function (len) {
var arr1 = [[]];
for (var i = 0; i < this.length; i++) {
if (arr1[arr1.length - 1].length === len) {
arr1.push([]);
}
arr1[arr1.length - 1].push(this[i]);
}
return arr1;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8];
var arr1 = arr.chunk(3);
console.log(arr1);
补充
//toString()的底层
Object.prototype.toString=function(){
return "[object "+this.constructor.name+"]"
}
var o = { a: 1 };
console.log(o.toString());//[object Object]
//通过toString.call() 修改
var arr=[1,2,3];
console.log({}.toString.call(arr))//[object Array]
console.log(arr.constructor.name)//Array
//constructor.name 当前对象的类型名称
var reg=/a/g;
console.log({}.toString.call(reg));//[object RegExp]
var a=1;
console.log({}.toString.call(a));//[object Number]
继承
`正常继承`
class Box {
static num = 1;
constructor(name) {
this.name = name;
}
play() {
console.log("play");
}
static run() {
console.log("run")
}
}
class Ball extends Box {
constructor(name, age) {
super(name);
this.age = age;
}
jump() {
console.log("jump");
}
play() {
super.play();
console.log("play2")
}
}
var b = new Ball("张三", 20)
console.log(b)
b.play()
`ES5中`
//相当于父类
function Box(name) {
this.name = name;
}
Box.num = 1;
Box.run = function () {
console.log("run")
}
Object.defineProperty(Box.prototype, "play", {
value() {
console.log("play")
}
})
//相当于子类
function Ball(name, age) {
Box.call(this, name)
this.age = age;
}
// 赋值父类的静态方法和属性到子类中
Object.assign(Ball, Box)
//继承父类的原型链
Object.setPrototypeOf(Ball.prototype, Box.prototype);
// 给子类添加新的属性和方法
Object.defineProperties(Ball.prototype, {
jump: {
value() {
console.log("jump");
}
},
play: {
value() {
// 执行超类的play方法
Box.prototype.play.apply(this, arguments);
console.log("play2");
}
}
})
var b = new Ball("张三", 30);
console.log(b)
Ball.run()
封装继承
//封装
Function.prototype.extends = function (superClass) {
var names = Object.getOwnPropertyNames(superClass).concat(Object.getOwnPropertySymbols(superClass)).slice(5);
for (var i = 0; i < names.length; i++) {
Object.defineProperty(this, names[i], Object.getOwnPropertyDescriptor(superClass, names[i]))
}
var proto = this.prototype;
function F() { }
F.prototype = superClass.prototype;
this.prototype = new F();
names = [...Object.getOwnPropertyNames(proto), ...Object.getOwnPropertySymbols(proto)];
for (var j = 0; j < names.length; j++) {
Object.defineProperty(this.prototype, names[j], Object.getOwnPropertyDescriptor(proto, names[j]));
}
Object.defineProperty(superClass.prototype, "super", {
value() {
superClass.apply(this, arguments);
}
})
this.prototype.superClass = superClass.prototype;
}
function Box(name) {
this.name = name;
}
Box.num = 1;
Box.run = function () {
console.log("run")
}
Object.defineProperty(Box.prototype, "play", {
value() {
console.log("play")
}
})
function Ball(name, age) {
this.super(name);
this.age = age;
}
Object.defineProperties(Ball.prototype, {
jump: {
value() {
console.log("jump");
},
},
play: {
value() {
this.superClass.play.apply(this, arguments);
console.log("play2")
},
},
})
Ball.extends(Box);
var b = new Ball("张三", 20);
console.log(b);
b.play();
浅复制
//仅复制第一层的数据,第一层数据改变不会引起改变,但是第二层以后的数据改变就会引起改变
//三种方式
1.var o=Object.assign({},obj);
2.var o={...obj};
3.var o={};
for(var key in obj){
o[key]=obj[key];
}
深复制
1.使用JSON字符串
var obj = {
a: 1,
b: 2,
c: {
d: 3,
e: 4
}
}
var o = JSON.parse(JSON.stringify(obj));
obj.c.d = 10;
console.log(o)
`缺点:`
1.只能处理对象和数组的数据,symbol不可以深遍历
2.丢失函数和原型信息
3.对于大型问题,有性能问题会消耗较多的时间和内存
4.JSON.stringify() 方法只能处理 JSON 可序列化的数据类型,例如字符串、数字、布尔值、数组、普通对象等。
它无法处理特定类型的对象,例如日期对象、正则表达式对象、Map、Set 等
5.处理循环引用问题困难
2.使用lodash中的_.cloneDeep可以进行深复制
`缺点`:不能赋复制不可枚举的 函数 原型
3.手写深复制
//这是一个对象
var d = Symbol();
var e = Symbol();
var ss = {
a: 1
};
var date = new Date();
var divs = document.createElement("div");
divs.setAttribute("a", "3");
date.setFullYear(2024);
function Box() {
}
Box.prototype.a = 10;
Box.prototype.play = function () {
console.log("aaa");
}
var obj = {
a: 1,
b: 2,
c: [1, 2, 3],
zz: new Set([1, 2, ss]),
yy: new Map(),
[d]: "aaa",
z: divs,
d: {
e: date,
f: /a/g,
g: function (s) {
console.log(s);
},
h: {},
},
ttt: Box
};
obj.d.g.ssss = 10;
obj.eee = new Uint8Array([97, 98, 99]);
Object.defineProperties(obj.d.h, {
i: {
value: 10,
},
j: {
configurable: true,
writable: true,
value: [1, 2, 3, 4],
},
k: {
writable: true,
value: {
l: {},
m: "abcde",
n: true,
o: [1, 2, 3],
},
},
[e]: {
value: ["a", "b", "c", "e"],
},
});
obj.z.style.width = "50px";
obj.z.style.height = "50px";
obj.z.style.backgroundColor = "red";
Object.defineProperties(obj.d.h.k.l, {
p: {
value: function (a,b) {
console.log("p");
},
},
q: {
value: {
r: {
a: 1
},
j: {
b: 2
},
},
},
});
var a_1 = {
a: 1
};
var a_2 = {
b: 2
};
obj.yy.set("name", "xietian");
obj.yy.set(a_1, a_2);
Object.defineProperty(obj, "www", {
set: function (_v) {
this.a = _v;
},
get: function () {
return this.a;
},
});
//深复制
function cloneDeep(source, target) {
if (target === undefined) {
switch (true) {
case source instanceof Node:
target = source.cloneNode(true);
break;
case source.constructor.__proto__.name === "TypedArray":
case source instanceof RegExp:
case source instanceof Date:
case source instanceof Set:
case source instanceof Map:
target = new source.constructor(source);
break;
default:
target = new source.constructor();
}
}
var names = Reflect.ownKeys(source);
for (var i = 0; i < names.length; i++) {
var key = names[i];
var desc = Object.getOwnPropertyDescriptor(source, key);
if (typeof desc.value === "object" && desc.value !== null) {
Object.defineProperty(target, key, {
configurable: desc.configurable,
writable: desc.writable,
enumerable: desc.enumerable,
value: cloneDeep(desc.value)
})
} else {
Object.defineProperty(target, key, desc);
}
}
return target;
}
var o = cloneDeep(obj);
obj.d.h.k.m = "你好"
console.log(o)