文章目录
参考网址
一、EC 与 ScopeChain
预解析
- 代码段是指一个script标签就是一个代码段。JS代码在执行时,是一个代码段一个代码段执行。
<!-- 代码段是彼此独立的,上面的代码段报错了,不会影响下面的代码段 -->
<!-- 1个script标签就是一个代码段 -->
<script>
// 在一个代码段中就可以写JS代码
var a = 110;
// 上面代码段不能使用下面的代码段中定义的数据
console.log(b); // ReferenceError: b is not defined
</script>
<!-- 一个网页中可以有多个代码段 -->
<script>
var b = 220;
// 可以在下面的代码段中使用上面的代码段中的数据
console.log(a);
</script>
- JS代码的执行分两个阶段:一个叫预解析,一个叫执行,预解析结束后,才会进入到执行阶段。
- 预解析
- 浏览器在执行JS代码的时候会分成两部分操作:预解析以及逐行执行代码
- 也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,
- 这个加工处理的过程, 我们就称之为预解析
- 预编译期间
- 把声明提升:加var的变量就是被提升,function声明的函数也会提升,提升到代码段的最前面。
- 函数内部的局部变量,提升到函数体的最前面。
- 注意:变量的提升仅仅是声明 函数的提升不只提升了声明,也提升赋值
- 规则是:
- 对于同名的变量声明,Javascript采用的是忽略原则,后声明的会被忽略,变量声明和赋值操作可以写在一起,但是只有声明会被提升,提升后变量的值默认为undefined,结果是在赋值操作执行前变量的值必为undefined
- 对于同名的函数声明,Javascript采用的是覆盖原则,先声明的会被覆盖,因为函数在声明时会指定函数的内容,所以同一作用域下一系列同名函数声明的最终结果是调用时函数的内容和最后一次函数声明相同
- 对于同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效
<script>
// --------- 分析
// 提升:fn整体
//
function fn(){
console.log(a); // 报错 找a 没a a is not defined
a = 666;
console.log(a); // 不执行
}
fn();
console.log(a); // 不执行
</script>
执行上下文和作用域链
- 先来总结下名词:
VO:全局变量对象。(早期)每一个执行上下文会被关联到一个变量环境(Variable Object,简称VO)新版(VE)
AO:活动对象 (也叫函数的变量对象)在解析函数时,会创建一个Activation Objec(AO);
GO:全局对象(window)Global Object(简称GO)
EC:执行上下文
ECG:全局执行上下文 Execution Context Global
ECStack:执行环境栈(调用栈)Execution Context Stack,简称ECS
JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)
JS/JavaScript中的概念区分:global对象、window对象、document对象
内存分区:
- 我们只需要掌握两个区,一个是栈区,一个是堆区。
- 基本数据类型,是存储在栈区的,引用数据类型是存储在堆区,堆区的地址,还是保存在栈区
全局代码和函数代码中产生的EC
- JS代码分两类:
- 全局代码:函数外面的代码都是全局代码
- 函数代码:一个函数就是一个局部代码
- 全局代码执行产生ECG(Execution Context Gloable),每当调用一个函数,就产生一个函数的EC。每产生一个EC,需要放到ECS(Execution Context Stack),当函数调用完毕,这个EC就是出栈,出栈后的EC,会被销毁,所谓的销毁指是它们分配的内存空间都要被释放掉。
注意(执行顺序不确定,没找到更权威的说明。从执行结果上也看不出有什么不同)
- 预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在var之上。(于老师也这样说的)
- 函数执行的时候,函数内部才会进行预解析,如果有参数,先给实参赋值再进行函数内部的预解析。(这个不确定,于老师说的是所有预解析在函数提升时就解析完成了)
- 预解析函数是声明+定义(开辟了内存空间,形参值默认是undefined)。
- 预解析变量是只声明,不赋值,默认为undefined。
- 函数重名时,后者会覆盖前者。
- 变量重名时,不会重新声明,但是执行代码的时候会重新赋值。
- 变量和函数重名的时候,函数的优先级高于变量。
执行上下文栈
- js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。
- Execute Context Stack ===> ECS
代码执行流程如下
JS在执行代码时,肯定先执行全局代码,就会产生EC(G),这个EC(G)就要入栈。当我们调用一个函数,就会产生一个局部的执行上下文,此时这个局部的执行上下文也要入栈。当函数调用完毕后,这个EC就要出栈,又进入EC(G),当全局代码执行完后,EC(G)也要出栈。
执行上下文的作用:
提供数据,全局代码,肯定需要去全局的执行上下文中找数据。
js引擎会在执行代码之前,会在堆内存中创建一个全局
- 对象:Global Object(GO)
- 该对象 所有的作用域(scope)都可以访问;
- 里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
- 其中还有一个window属性指向自己;说白了,GO就是window
- 只要我们写的全局变量或在全局中写的函数,都会挂载到window上面
ECG被放入到ECS中里面包含两部分内容:
- 第一部分:在代码执行前,在parser(解析器)转成AST(Abstract syntax tree,即抽象语法树)的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值;这个过程也称之为变量的作用域提升(hoisting)
- 第二部分:在代码执行中,对变量赋值,或者执行其他的函数;
遇到函数
- 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中。
- ECF中包含三部分内容:
- 第一部分:在解析函数成为AST树结构时,会创建一个Activation Object(AO): AO中包含形参、arguments、函数定义和指向函数对象、定义的变量;
- 第二部分:作用域链:由VO(在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
- 第三部分:this绑定的值:这个我们后续会详细解析;
- 当执行上下文进入执行阶段后,变量对象会变为活动对象(Active Object)。此时原先声明的变量会被赋值。
- 变量对象和活动对象都是指同一个对象,只是处于执行上下文的不同阶段。
找一个函数的父的EC,看的是函数的定义处,不是调用处
<script>
var n = 100;
// 找一个函数的父的EC,看的是函数的定义处,不是调用处
function fn(){
console.log(n);
}
function gn(){
var n = 200;
console.log(n);
fn()
}
gn()
console.log(n);
</script>
二、深入变量 与 闭包
加var的变量和不加var的变量
- 加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
- 不管有没有加var,创建的全局变量,都会放到GO中的,也就是可以通过window.xx
- 加var的变量,可能是全局变量,也可能是局部变量,不加var的变量,只能是全局变量。
- 加var的局部变量,不会作用window的属性
- 使用var声明变量不足:
- 提升
- 重复声明 浏览器中有这样一个机制,如果一个变量提升过了,后面遇到同名变量,就不会提升了。
let
- 使用let声明的变量没有提升。针对这个错误:ReferenceError: Cannot access ‘a’ before initialization有人是这样说的:let声明的变量也有提升。但是没有赋值(没有初始化),就记一种:使用let声明的变量也提升,只不过没有初始化,去使用的时候就报错了。
- 使用let配合{}会形成块级作用域。 在C语言中,就有块级作用域。在JS中,原本是没有块级作用域。在块中声明的变量,只能在块中使用。
- 使用let声明的变量并不会挂载到GO中
- 使用let声明的变量也不能重复声明
const
- 声明的变量不能修改(常量)
- 声明时必须赋值(初始化) 定义 = 声明+赋值(初始化)
- const声明的常量也不会提升
- const声明的常量配合{} 也会形成块级作用域
- const声明的常量也不会挂载到GO上。
例题(更多看参考地址)
<script>
// var a = 12; var b = 13; var c = 14;
var a = 12; b = 13; c = 14;
function fn(a){
// 形参a是函数内部的局部变量a 形参a的值是10
console.log(a,b,c); // 10 13 14
a = 100; // 找a 把100赋值给了局部变量a
b = 200; // 找b 把200赋值给外面的b 是全局变量b
console.log(a,b,c); // 100 200 14
}
b = fn(10); // und
console.log(a,b,c); // 12 und 14
</script>
注意下面这个例子
深探内部,let和const,应该也是提升了,但是没有被初始化(没有undefined)。{个人理解没找到更权威的解释}
<script>
// var a = 1;
// let a = 2; // Identifier 'a' has already been declared
function sum(b) {
// 形参是相当于函数内部局部变量
console.log(b);
// let b = 5
const b = 5// 报错了Uncaught SyntaxError: Identifier 'b' has already been declared
}
sum(200);
</script>
JS中函数是一等公民
- 在JavaScript中,函数是非常重要的,并且是一等公民:
函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用 - 高阶函数:
一个函数它的参数是函数或它的返回值是函数,那么这个函数就是高阶函数。
回调函数
被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。
闭包
(mdn)的定义:
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
-
函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量,即形成了闭包。
-
闭包是一片不会被销毁的EC。它对EC中的数据有保护和保存的作用:
- 保护:防止像全局变量那样被污染,数据在函数内部,外界访问不了。
- 保存:可以像全局变量那样,延长变量的生命周期。
- 闭包使用不当可能会导致内存泄露
-
应用场景:
- 模块封装,在各模块规范(ES6)出现之前,都是用这样的方式防止变量污染全局
- 数据私有化
- 封装一些工具函数
- 在循环中创建闭包,防止取到意外的值
-
解决内存泄漏
- 手动将fn函数以及foo函数指向为空地址既可以释放闭包产生的内存泄漏,fn = null
- 在函数内部声明一个"_this" 来解决这个this指向问题。
这个方法在Vue中做一个ajax请求的时候比较常使用,每一次都声明一个_this,解决闭包留下来的泄漏问题,而且指向的对象比较有唯一性。
let _this = this; return function() { return _this.user; };
- 通过箭头函数来解决,这个方法最快;通过箭头函的特殊性来解决。
return () => { return this.user; }
闭包小应用
var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
(function (index) {
lis[index].onclick = function () {
console.log(index);//此处产生闭包
};
})(i);}
//let写法,let会产生块级作用域-------------------
let lis = document.querySelectorAll('li')
for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i);
};
}
IIFE
Immediately Invoked Function Expression(立即调用函数表达式)
<script>
// IIFE写法一
(function fn() {
console.log("fn...");
})()
</script>
<script>
// IIFE写法二
(function fn() {
console.log("fn...");
}())
</script>
<script>
// IIFE写法三
+function fn() {
console.log("fn...");
}()
</script>
<script>
// IIFE写法四
-function fn() {
console.log("fn...");
}()
</script>
<script>
// IIFE写法五
!function fn() {
console.log("fn...");
}()
</script>
必须用;
隔开不然一二写法会报错。
因为立即调用函数表达式特性,标识符无法被修改
例题:
var b = 10;
(function b() {
b = 20;
console.log(b);
})();//输出 b的函数体
console.log(b);//输出 10
三、this
- 函数在调用时,JavaScript会默认给this绑定一个值;
- this的绑定和定义的位置(编写的位置)没有关系;
- this的绑定和调用方式以及调用的位置有关系;
- this是在运行时被绑定的;
<script>
// 谁也说不清,现在的this是谁
// 只有代码运行起来后,JS底层才会给this赋值
// this最终值和你书写的位置没有关系
// 和你如何调用fn函数是有关系,不同的调用方式,会给this赋不同的值
function fn() {
console.log(this);
}
</script>
this的绑定规则
- 绑定一:默认绑定;
- 绑定二:隐式绑定;
- 绑定三:显示绑定;
- 绑定四:new绑定;
注意
- this在产生EC时,会动态给this绑定值,没有产生EC,是不能确认this是什么,并不能给this手赋值
默认绑定
独立函数调用就是所谓的默认绑定,独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
<script>
// --------------- 独立的函数调用
function fn() {
console.log(this);
}
// 独立的函数调用
// 函数内部的this,在浏览器中表示window
fn();
</script>
隐式绑定
另外一种比较常见的调用方式是通过某个对象进行调用的,也就是它的调用位置中,是通过某个对象发起的函数调用
显示绑定
-
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性);
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
- 正是通过这个引用,间接的将this绑定到了这个对象上;
-
JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
-
通过call,apply,bind可以改变this指向。
call
call的作用:1)显示绑定this 2)让函数执行
apply
apply的作用:1)显示绑定this 2)让函数执行(参数是数组)
bind
bind的作用:1)显示绑定this 2)不会让函数执行 3)返回一个绑定this之后新函数
注意
<script>
function fn() {
console.log("fn...");
console.log(this);
}
fn.call("hello"); // 会把"hello"包装成一个新的对象
fn.call(undefined); // 参数是und this指定window
fn.call(null); // 参数是null this指定window
</script>
显示绑定bind应用
<body>
<button id="btn">点击事件</button>
<script>
const user = {
data: [
{ name: "kingx1", age: 11 },
{ name: "kingx2", age: 12 }
],
clickHandler: function () {
// 随机生成整数0或1
// let randomNum = ((Math.random() * 2 | 0) + 1) - 1;
let randomNum = ~~(Math.random() * 2);
// 从data数组里随机获取name属性和age属性,并输出
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
};
const button = document.getElementById('btn');
button.onclick = user.clickHandler.bind(user);
</script>
</body>
new绑定
构造函数
- ** JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接;
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象;
<script>
// --------------- new运算符
// 函数的几种角色:
// 1)普通函数
// 2)对象的方法
// 3)普通的对象
// 4)类 new一个类,就得到一个对象
// 如果你要把函数当成一个类,建议把函数名首字母大写
function Fn() {
// new做了什么
// 1)在类中创建一个空的对象
// 2)把这个对象绑定到类中的this上面
// 3)返回这个空对象
this.name = "wc";
this.age = 18;
}
let obj = new Fn(); // new一个类,得到一个对象
console.log(obj); // {name: 'wc', age: 18}
</script>
<script>
// 类 构造器
// JS内置了一些构造器 String Number Boolean
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person("wc", 18);
console.log(p1);
let p2 = new Person("xq", 20);
console.log(p2);
</script>
如果return的是引用类型Array,Date,Object,Function,RegExp,Error的话,构造函数和普通函数都会返回return后面的值
function Person() {
var arr = [];
this.name = "zqq";
this.age = 28;
return arr;
}
var p = new Person();//返回arr空数组,Date,Object,Function,RegExp,Error同理
var p1 = Person();//返回arr空数组,Date,Object,Function,RegExp,Error同理
console.log(p, p1);
内置函数的绑定
<script>
// 定时器中的this表示window。
setTimeout(function() {
console.log(this);
}, 3000)
setInterval(function() {
console.log(this);
}, 2000);
</script>
<div id="box">click me</div>
<script>
// 得到id为box的元素 dom元素是一个对象
// 对象是引用数据类型
let box = document.getElementById("box");
// box表示事件源 事件发生的场所
// click表示事件类型 JS中有非常多不同类型的事件
// function(){} 表示当点击事件发生了做什么 监听器
box.onclick = function() {
console.log("hello DOM~");
console.log(this); // 监听器中的this表示事件源
}
</script>
<script>
let names = ["wc", "xq", "z3"];
names.forEach(function(item) {
console.log(item, this);
}, {
name: "ok"
})
</script>
忽略显示绑定
如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则
<script>
function fn() {
console.log(this);
}
fn.apply({
name: "wc"
})
// 忽略显示绑定
fn.apply(null) // window
fn.apply(undefined) // window
var gn = fn.bind(null);
gn();
</script>
间接函数引用
另外一种情况,创建一个函数的 间接引用,这种情况使用默认绑定规则
- 赋值(obj2.foo = obj1.foo)的结果是foo函数;
- foo函数被直接调用,那么是默认绑定;
<script>
var obj = {
name: "wc",
fn: function() {
console.log(this);
}
}
var obj2 = {
name: "xq"
};
// obj2.gn = obj.fn;
// obj2.gn(); // 隐式绑定
// IIFE var a = 1;
// IIFE this表示window
;
(obj2.gn = obj.fn)();
;
(function() {
console.log(this);
}())
</script>
规则优先级
- 默认规则的优先级最低
- 显示绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于bind
- new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
- new绑定可以和bind一起使用,new绑定优先级更高
<script>
function fn() {
console.log(this);
}
// bind返回改变this指向后的新函数
var gn = fn.bind({
name: "wc"
});
// gn();
// this 是 {} 说明:new绑定高于显示绑定
var kn = new gn();
</script>
<script>
function fn() {
console.log(this);
}
// new 不能和call和apply一起使用
// call 可以让fn执行 gn不是函数
var gn = fn.call({
name: "wc"
});
console.log(gn);
var kn = new gn();
</script>
ES6箭头函数
<script>
// var fn = function(num1,num2){
// return num1 + num2;
// }
// 像上面的函数可以简写
var fn = (num1, num2) => {
return num1 + num2;
}
console.log(fn(1, 2));
</script>
<script>
// 如果形参只有一个,()可以不写
var fn = num => {
return num * 10;
}
console.log(fn(10));
</script>
<script>
// 如果函数体中只有一行语句,{}可以不写
var fn = num => console.log(num + 100);
fn(100)
</script>
<script>
// 如果函数体中只有一行代码,并且这行代码是return语句
// {}可以不写 return也可以不写
var fn = num => num * 10;
console.log(fn(10));
</script>
<script>
// 如果函数体中只有一行代码,并且是return语句
// return了一个对象
// var fn = function(){
// return { name:"wc" }
// }
// 把上面的代码转成箭头函数
// 此时它会把对象的{} 当成函数的{}
// 需要使用()把对象包起来
var fn = () => ({
name: "wc"
})
console.log(fn());
</script>
ES6箭头函数this
箭头函数并不绑定this对象,this引用从上层作用于中找到对应的this。
四、OOP(面向对象)
创建对象的两种方式
<script>
// 创建方式一:new Object创建
// Object 类 构造器 构造函数 函数也是类
// 函数有多种角色:1)普通函数 2)方法 3)对象 4)类
// 对象是属性的无序集合 操作集合 CRUD
let obj = new Object();
obj.name = "wc";
obj.age = 18;
obj.height = 1.88;
obj.running = function(){
console.log("running...");
}
console.log(obj.name);
console.log(obj.age);
obj.running();
// 创建方式二:通常字面量的形式
let obj2 = {
name:"xq",
age:20,
eat:function(){
console.log("eat...");
}
}
console.log(obj2.name);
obj2.eat();
</script>
JS中内置的类
<script>
// Number是类,也叫构造器
// 通过new运算符,得到一个对象
// num1 百分之百是对象 var a = 110; a不是对象 a是number基本数据类型
// 引用数据类型存储在堆区 num1中存储了地址 指向那个堆
let num1 = new Number(110);
console.log(num1);
console.dir(num1); // dir是用来打印对象
</script>
<script>
// String是类 也叫构造器 构造函数
// let str1 = "hello"; // str1不是对象
// str1.toUppercase(); str1会包装成对象
let str = new String("hello");
console.log(str);
</script>
<script>
// let f = false; f不是对象
let b = new Boolean(true);
console.log(b);
</script>
<script>
let obj = new Object();
obj.name = "wc";
console.dir(obj);
</script>
<script>
let arr = new Array("wc","xq");
console.dir(arr);
</script>
运算符:instanceof in hasOwnProperty
instanceof 判断一个对象是否属于一个类
<script>
let obj = new Object();
obj.name = "wc";
// 判断obj是否是Object的对象
console.log(obj instanceof Object); // true
console.log(obj instanceof Number); // false
</script>
in
如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true。
hasOwnProperty()
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
【+】- for…of与for…in的区别
- for…in语句以任意顺序迭代对象的可枚举属性。
- for…of 语句遍历可迭代对象定义要迭代的数据。
一切都是对象
对象是属性的无序集合
增删改查
// . 属性访问运算符
console.log(obj.name);
console.log(obj.age);
// [] 也可以访问对象中的属性
console.log(obj['name']);
console.log(obj['age']);
// delete是一个运算符
delete obj.name;
对属性操作的控制
- 属性描述符
- 通过属性描述符可以精准的添加或修改对象的属性;
- 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改;
- Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象;
- 三个参数:
- obj要定义属性的对象;
- prop要定义或修改的属性的名称或 Symbol;
- descriptor要定义或修改的属性描述符;
【注】symbol 是一种基本数据类型
- Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。
- 每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
- 返回值:被传递给函数的对象
- 属性描述符分类,属性描述符的类型有两种
- 数据属性(Data Properties)描述符(Descriptor)
- 存取属性(Accessor访问器 Properties)描述符(Descriptor)
数据属性描述符
数据数据描述符有如下四个特性:
-
[[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;
- 和数据属性描述符是一致的
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
- 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
-
[[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;
- 和数据属性描述符是一致的;
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true;
- 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
-
[[Writable]]:表示是否可以修改属性的值;
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true;
- 当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false;
-
[[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
- 默认情况下这个值是undefined;
<script>
let obj = {
name:"wc",
age:18
}
// Object也是对象
// 第三个参数是属性描述符,对height这个属性进行描述
// 这样写,可以对height属性做精细化设置
Object.defineProperty(obj,"height",{
value:1.88
})
console.log(obj.height);
</script>
<script>
let obj = {
name:"wc",
age:18
}
// value 是给属性赋的值
// configurable:配置属性是否可以删除
Object.defineProperty(obj,"address",{
value:"BJ",
// configurable如果是false,表示此属性不能删除
configurable:false,
// enumerable表示属性是否可以被枚举 false表示不能枚举
enumerable: false,
// writable表示是否可以给属性重新赋值
writable:false
})
console.log(obj.address);
delete obj.address
console.log(obj.address);
for(let key in obj){
console.log(obj[key]);
}
obj.address = "SH";
console.log("----",obj.address);
</script>
存取属性描述符
数据数据描述符有如下四个特性
<script>
let obj = {
name:"wc",
age:18,
// 行业潜规则:如果属性以_打头,表示私有属性
_address:"bj"
}
//
Object.defineProperty(obj,"address",{
enumerable:true, // 可以枚举
configurable:true, // 可以删除
get:function(){ // 获取器
// console.log("get...");
return this._address; // this表示obj
},
// 当给address设置新的值时会走设置器
set:function(value){ // 设置器 value就是设置的新值
// console.log("set...");
this._address = value;
}
})
// 当obj.address时,它会自动调用获取器
console.log(obj.address);
obj.address = "sh";
console.log(obj.address);
</script>
同时定义多个属性
Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象。
<script>
let obj = {
_age:18,
}
Object.defineProperties(obj,{
name:{
value:"wc",
configurable:true,
enumerable:true,
writable:true
},
age:{
configurable:true,
enumerable:true,
get:function(){
return this._age;
},
set:function(value){
this._age = value;
}
}
})
console.log(obj.name);
console.log(obj.age);
obj.age = 20;
console.log(obj.age);
</script>
获取属性描述符
- getOwnPropertyDescriptor
- getOwnPropertyDescriptors
<script>
let obj = {
_age: 18, // 不想让别人访问
}
Object.defineProperties(obj, {
name: {
value: "wc",
configurable: true,
enumerable: true,
writable: true
},
age: {
configurable: true,
enumerable: true,
get: function () {
return this._age;
},
set: function (value) {
this._age = value;
}
}
})
// 获取name属性的描述符
console.log(Object.getOwnPropertyDescriptor(obj,"name"));
// 获取对象的所有属性的描述符
console.log(Object.getOwnPropertyDescriptors(obj));
</script>
工厂函数创建对象
<script>
// 工厂函数 目的:产生对象
function createPerson(name,age,height,address){
let p = {};
p.name = name;
p.age = age;
p.height = height;
p.address = address;
p.running = function(){ console.log(this.name + "在跑步~"); }
return p;
}
let wc = createPerson("wangcai",13,160,"bj"); wc.running();
let xq = createPerson("xiaoqiang",14,170,"gz"); xq.running();
let zs = createPerson("zhangsan",18,180,"sz"); zs.running();
console.log(wc instanceof Object);
console.log(xq instanceof Object);
console.log(zs instanceof Object);
</script>
- 缺点:获取不到对象最真实的类型
使用构造器创建对象
- 构造器,是JS中特有的,类似一个类,也叫构造函数,英文叫constructor,需要通过new一个构造器,就可以产生一个对象。
- new
- 在构造器内部创建一个新的对象
- 这个对象内部的prototype属性会被赋值为该构造函数的prototype属性;
- 让构造器中的this指向这个对象
- 执行构造器中的代码
- 如果构造器没有返回对象,则返回上面的创建出来的对象
<script>
// 构造器,也就是类,首字母需要大写
function Fn(){
console.log("Fn...");
}
// Fn(); // 普通函数调用
// ()可加可不加
let wc = new Fn(); // obj对应的类是Fn
console.log(wc);
</script>
<script>
// 构造器
function Person(name,age,height,address){
this.name = name;
this.age = age;
this.height = height;
this.address = address;
this.runing = function(){ console.log(this.name + " running..."); }
}
let wc = new Person("wangcai",19,180,"bj"); wc.runing();
let xq = new Person("xiaoqiang",18,160,"gz"); xq.runing();
let zs = new Person("zhangsan",15,150,"sz"); zs.runing();
</script>
- 缺点:可能造成内存空间浪费。
使用构造器+原型创建对象
五、原型与原型链
原型分为隐式原型(proto)和显示原型(prototype)
也可以叫对象原型(proto)和原型对象(prototype)
公有属性和私有属性
- 一切都是对象
- 对象是属性的无序集合
- 每一个对象上都有一个叫_ _ proto _ _这样的属性,是一个属性名
- _ _ proto _ _对应的属性值是一个对象,这个对象叫隐式原型对象
作用域链
- 作用域链:在EC中查找数据的机制。
- 原型链:在对象中查找属性的机制。
每一个函数都有一个prototype属性
constructor
每一个原型对象上都有一个叫constructor属性,指向它对应的构造器
new
(1)在构造器内部创建一个新的对象
(2)这个对象内部的prototype属性会被赋值为该构造函数的prototype属性;
(3)让构造器中的this指向这个对象
(4)执行构造器中的代码
(5)如果构造器没有返回对象,则返回上面的创建出来的对象
静态属性
静态属性是绑定在类的属性,类是构造器,构造器是函数,函数也是对象。
<script>
function Person(name) {
this.name = name; // 对象自己的私有属性
// Person叫构造器 类 构造函数
// 函数有多种角色 对象
Person.total = 110; // total是们于Person这个类的
// 位于类上的属性,叫静态属性
}
let wc = new Person("wc")
let xq = new Person("xq")
console.log(wc.name);
console.log(xq.name);
// 访问静态属性必须通过类名
console.log(Person.total);
</script>
补充运算符
- in(判断不管私有还是公有属性)
如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true。 - hasOwnProperty()(判断私有属性)
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
对象要链式查找,直到指向null返回undefined,变量要看作用域
<script>
function Fn() {
let a = 1;
this.a = a;
}
Fn.prototype.say = function() {
this.a = 2;
}
Fn.prototype = new Fn();//这里会覆盖prototype,但是创建时构造函的,prototype已经有say。
let f1 = new Fn();
Fn.prototype.b = function() {
this.a = 3;
}
console.log(f1.a)
f1.say();//f1没有say,向上查找
console.log(f1.a)
f1.b();
console.log(f1.a)
</script>
六、继承
继承, 可以让多个构造函数之间建立关联, 便于管理和复用
原型继承
// 目标: 原型继承 => 继承方法
// 原型继承: 通过改造原型链实现继承, 利用原型链的特征实现继承,所谓的原型继承,就是在改造原型链
// 1. 定义Person构造函数
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log('人类会说话')
}
// 2. 定义Student构造函数
function Student (name, age, className) {
this.name = name
this.age = age
this.className = className
}
// 3. 原型继承: 利用原型链, 继承于父级构造函数, 继承原型上的方法
// 语法: 子构造函数.prototype = new 父构造函数()
Student.prototype = new Person()
Student.prototype.constructor = Student;
Student.prototype.study = function() {
console.log('学生在学习')
}
let stu = new Student('张三', 18, '80期')
stu.say()
console.log(stu)
- 原型链继承的缺点:
- 如果父中的数据类型是引用数据类型,子对象修改了,另一个子对象也会受影响
- 创建Child对象时,不能传参
继承之组合继承
// 组合继承: 两种技术的组合, 原型链技术, 借用构造函数(call)结合, 发挥二者之长, 实现继承的方式
// 1. 原型链技术: 改造原型链, 实现继承方法
// Student.prototype = new Person()
// 2. 实例属性的构造过程没有得到复用, 可以用借用构造函数的方式, 实现复用,(call无法继承原型对象上的方法)
// Person.call(this, name, age)
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log('会说话')
}
function Student (name, age, className) {
// 不仅要执行Person构造函数, 且要让执行构造函数时的this指向创建出来的实例stu
// call
// 1. 调用函数
// 2. 改变函数执行时的this指向
Person.call(this, name, age)
this.className = className
}
Student.prototype = new Person()
const stu = new Student('zs', 7, '一年级一班')
stu.sayHi()
console.log(stu)
// 方法通过 原型继承
// 属性通过 父构造函数的.call(this, name, age)//
继承之寄生组合继承
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log('会说话')
}
function Student (name, age, className) {
// 不仅要执行Person构造函数, 且要让执行构造函数时的this指向创建出来的实例stu
// call
// 1. 调用函数
// 2. 改变函数执行时的this指向
Person.call(this, name, age)
this.className = className
}
// 构造函数没有必要执行, 我们只需要的是原型链
Student.prototype = Object.create(Person.prototype)
const stu = new Student('zs', 7, '一年级一班')
stu.sayHi()
console.log(stu)
// 总结:
// Object.create() 以参数的对象, 作为新建对象的__proto__属性的值, 返回新建的对象
Object.create() 指定proto
- proto 参数需为
- null 或
- 除基本类型包装对象以外的对象
继承之ES6中的继承
// function Person (name, age) {
// this.name = name
// this.age = age
// }
// Person.prototype.sayHi = function() {}
// Person.prototype.jump = function() {}
// 人类
class Person {
// 类似于之前的构造函数
constructor (name, age) {
this.name = name
this.age = age
}
// 底层 => 这两个方法, 就是添加到 Person.prototype 上
sayHi () {
console.log('你好哇')
}
jump () {
console.log('会跳')
}
}
const p = new Person('zs', 18)
console.log(p)
// 继承关键字 => extends
// 老师类
class Teacher extends Person {
// 如果没有提供构造函数, 在继承时, 会默认自动借调父构造函数
constructor (name, age, lesson) {
// 你写的构造函数中, 没有借调父构造函数
super(name, age) // 触发调用父构造函数, 进行实例的属性初始化
this.lesson = lesson
}
teach () {
console.log('会教书')
}
}
const teacher = new Teacher('zs', 18, '教体育')
console.log(teacher)
七、with,eval,严格模式
with(mdn)
- with语句,作用:扩展一个语句的作用域链。
- 在开发中,不要使用with,with语句破坏了作用域链,可能会出现一些错误,也有兼容性问题。
eval
eval是一个特殊的函数,作用:可以把一片字符串,当成JS代码运行。
<script>
let age = 110;
var jsStr = " var msg = 'haha'; console.log(msg); console.log(age); ";
// 可以把上面的字符串当成JS代码运行
// eval执行代码,并不是沙箱环境,受外界的环境影响
// 在node中,一个运行JS代码的沙箱环境
eval(jsStr);
console.log(msg);
</script>
- 在开发中,不要使用eval,缺点:
- 可读性非常差
- 本质是字符串,在执行过程中,可能会被恶意篡改,有被攻击的风险
- eval执行的代码并不会被JS引擎优化
严格模式
- 在一个JS文件中开启 “use strict” 这个文件中写的代码都受严格模式的约束
- 在一个函数中开启格式模式 function fn(){ “use strict” xxx } 其它代码不受约束
"use strict"
// 1)不能使用没有加var的全局变量
name = "wc";
console.log(name);
function foo() {
age = 18;
console.log(age);
}
foo();
// 2)形参不能重名
function fn(x, y, z, x) {
console.log(x, y, z, x);
}
fn(1, 2, 3, 4)
// 3)不能使用老的8进制数据的写法
let num1 = 0x123; // 16进制
let num2 = 0o10; // 8进制(新的8进制写法)
let num3 = 010; // 8进制(老的8进制写法)
console.log(num1);
console.log(num2);
console.log(num3);
// 4)不能使用with语句
let obj = {
msg: "hello"
}
with(obj) {
console.log(msg);
}
// 5)在严格模式下,JS串中定义的数据,外界不能使用
// 外界定义的数据,在JS串中还是可以使用
let age = 110;
let jsStr = " 'use strict'; var msg = 'haha'; console.log(age); ";
console.log(msg);
eval(jsStr);
// 6)在非严格模式下,this是window 在严格模式下,不会默认绑定,this是und
setTimeout(() => {
console.log(this);
}, 2000)
function fn() {
console.log(this);
}
fn();
<script>
function fn() {
// 在函数内部去开启严格模式
"use strict";
a = 110;
console.log(a);
}
fn();
</script>
八、函数式编程
柯里化
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
<script>
// 未柯里化的函数
function add(x,y,z){
return x+y+z;
}
console.log(add(20,10,30));
// 柯里化处理的函数
function add2(x){
return function(y){
return function(z){
return x+y+z;
}
}
}
console.log(add2(10)(20)(30));
</script>
<script>
let add2 = x=>y=>z=>x+y+z;
console.log(add2(10)(20)(30));
</script>
柯里化有 3 个常见应用
- 参数复用 – 当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选
- 提前返回 – 多次调用多次内部判断,可以直接把第一次判断的结果返回外部接收
- 延迟计算/运行 – 避免重复的去执行程序,等真正需要结果的时候再执行
函数式编程
- 让函数职责更加单一,在函数式编程,我们希望,一个函数处理问题尽可能单一,而不是将一大堆的处理过程交给一个函数来处理,所以,我们给函数传入的参数,应该非常灵活,可以先传入一部分参数,处理完后,再传其它参数。
- 柯里化可以复用代码
<script>
function sum(m,n){
return m+n;
}
console.log(sum(10,1));
console.log(sum(10,2));
console.log(sum(10,3));
console.log(sum(10,4));
function sum2(count){
return function (num){
return count + num;
}
}
let makeSum = sum2(10)
console.log(makeSum(1));
console.log(makeSum(2));
console.log(makeSum(3));
console.log(makeSum(4));
</script>
组合函数
<script>
function double(num){
return num*2;
}
function square(num){
return num ** 2;
}
// console.log(double(10));
// console.log(square(10));
// 需求:给一个数字,先乘以2,再平方
let count = 12;
let res = square(double(count))
console.log(res);
</script>
九、JSON,数据存储,异常
JSON
JSON: JavaScript Object Notation JS对象描述符
- 格式一
"hello json"
- 格式二
{
"name": "wc",
"age:": 19,
"friend": {
"name": "xq"
},
"hobbies":["zq","lq"]
}
- 格式三
[
"abc",123,{"name:":"wc"}
]
- JSON的语法要求:
- 支持的数据类型:数字,字符串,布尔值,null值,不能写函数
- 对象的key必须使用双引号包起来
- JSON中没有注释
- 通过一些网站可以验证自己的JSON是否OK
- JSON是服务器响应给客户端的数据格式 也可以把JSON数据给服务器 所以说 JSON是客户端与服务器之间通信的一种数据格式。是目前最最常用的数据格式。
- JSON并不是JS对象,和JS对象的区别
JSON的序列化和反序列化
- obj对象转成JSON串之后,再存储,这个过程,叫序列化
<script>
let obj = {name:"wc"}
let objstr = JSON.stringify(obj); // 转成了JSON串
console.log(objstr); // {"name":"wc"}
console.log(typeof objstr); // string JSON字符串
window.localStorage.setItem("obj",objstr)
</script>
- 反序列化:把JSON串还原成JS对象
<script>
let str = window.localStorage.getItem("obj")
console.log(str);
console.log(typeof str);
let obj = JSON.parse(str);
console.log(obj.name);
</script>
- 利用JSON可以实现深copy
<script>
let obj = {name:"wc"}
// let newObj = obj; // 浅copy
let objstr = JSON.stringify(obj)
let newObj = JSON.parse(objstr);
obj.name = "wc666";
console.log(newObj.name);
</script>
数据存储
- 数据可以存储在两个地方:
- 内存
- 硬盘
- Local Storage,Session Storage,Cookies
LocalStorage
- LocalStorage是永久化数据存储方案,数据存储完毕,关闭浏览器,数据还是存在的。
- API
- length 返回数据的数量
- setItem(key,value) 存储数据的 如果key一样,数据会发生覆盖
- getItem(key) 获取数据
- removeItem(key) 删除数据
- Clear() 清除local storage中的所有的数据
<script>
// 存储数据
// window.localStorage.setItem("name","wc")
// window.localStorage.setItem("age",18)
// 获取数据
// console.log(localStorage.getItem("name"));
// 删除数据
// localStorage.removeItem("age")
// 清除数据
localStorage.clear();
</script>
SessionStorage
- Setssion Storage中也可以存储数据,它的数据的有效性是在一次会话,当会话结束,数据会自动被销毁,或你跳到一个新的网页,sessionStorage中的数据也会被销毁。
- 对应的API,和localStorage中的API是一样的。
封装localStorage和sessionStorage
// Cache是缓存的意思
// Cache还需要区域是永久性存储还是会话级存储
class Cache {
constructor(isLocal = true){
this.storage = isLocal ? localStorage : sessionStorage;
}
setItem(key,value){
if(value){
this.storage.setItem(key,JSON.stringify(value))
}
}
getItem(key){
let value = this.storage.getItem(key)
if(value){
value = JSON.parse(value);
return value;
}
}
removeItem(key){
this.storage.removeItem(key)
}
clear(){
this.storage.clear();
}
length(){
return this.storage.length;
}
}
// 如果不传参或传一个true 表示永久存储
let localCache = new Cache();
// 如果传入false,表示会话级存储
let sessionCache = new Cache(false);
//使用
<script>
// 由于上面的代码段中定义了localCache和sessionCache
// 在下面的代码段中就可以使用localCache和sessionCache
localCache.setItem("address","bj")
console.log(localCache.getItem("address"));
</script>
JS中的常见错误
语法错误
Uncaught SyntaxError
<script>
var a = 1;
function fn{}
</script>
引用错误
- 错误之前的代码还会执行
- 错误之后的代码不会执行
- 通常情况下,我们使用了一个没有声明的数据,就会报这样的错误
- Uncaught ReferenceError: b is not defined
<script>
console.log(1);
console.log(a);
var a = 3;
console.log(b);
console.log(5);
</script>
范围错误
demo.html:20 Uncaught RangeError: Invalid array length
<script>
let arr = new Array(-2);
console.log(arr);
</script>
自己抛出错误和捕获错误
throw语句
<script>
function sum(num1,num2){
if(typeof num1 !== "number" || typeof num2 !== "number"){
throw new Error("你的参数不合适,请传入数字")
// throw "你的参数不合适,请传入数字"
}
return num1+num2;
}
// 错误后面的代码不会执行了
console.log(sum(10,null));
console.log("我是后面的代码");
</script>
- 错误也有一个类,叫Error,也应对了很多了类,如RangeError,SyntaxError,TypeError… 我们就可以new 一个错误对象抛出去
- 如果代码出错了,下面的代码就不会执行了
try catch
出错,后面依然可以执行
<script>
function sum(num1,num2){
if(typeof num1 !== "number" || typeof num2 !== "number"){
throw new Error("你的参数不合适,请传入数字")
}
return num1+num2;
}
try{
// try中放可能出错的代码
sum(10,null)
}catch(err){
// catch中就可以捕获,程序员或JS引擎抛出的错误
console.log(err);
}
console.log("我是后面的代码");
</script>
十、数据类型检测和方法重构
数据类型检测
typeof
- 可以判断数据类型,它返回表示数据类型的字符串(返回结果只能包括number,boolean,string,function,object,undefined);
- 可以使用typeof判断变量是否存在(如if(typeof a!=“undefined”){…});
- Typeof 运算符的问题是无论引用的对象是什么类型 它都返回object
// 利用typeof检测基本类型和函数,非常准确
console.log(typeof 123); // number
console.log(typeof NaN); // number
console.log(typeof "hello"); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof function () { }); // function
// 检测其它的,结果都是object
console.log(typeof null); // object
console.log(typeof {}); // object
console.log(typeof []); // object
instanceof
console.log(arr instanceof Array ); // true
console.log(date instanceof Date ); // true
console.log(fn instanceof Function ); // true
//注意: instanceof 后面一定要是对象类型,大小写不能写错,该方法试用一些条件选择或分支
- instanceof 的不足:
- 1)可以检测引用数据类型,不能检测基本数据
- 2)所有的引用数据类型,都是Object的实例
- 3)可以人为修改原型链,导致检测的结果不准备
通过Object下的toString.call()方法来判断
- 这个最准确
Object.prototype.toString.call();
console.log(toString.call(123)); //[object Number]
console.log(toString.call('123')); //[object String]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call({})); //[object Object]
console.log(toString.call([])); //[object Array]
console.log(toString.call(function(){})); //[object Function]
根据对象的contructor判断
console.log('数据类型判断' - constructor);
console.log(arr.constructor === Array); //true
console.log(date.constructor === Date); //true
console.log(fn.constructor === Function); //true
- 不足:
- 1)可以检测引用数据类型,不能检测基本数据类型
- 2)人为可以修改constructor指向 ,检测的结果就不准
方法重构
简单实现new
//封装mynew
function mynew(Fn, ...args) {
if (!Fn.hasOwnProperty('prototype')) {
throw new TypeError(`${Fn} is not a constructor`)
}
let obj = Object.create(Fn.prototype)
let result = Fn.apply(obj, args)
return result !== null && (typeof result == 'object' || typeof result == 'function') ? result : obj
}
//使用
function Ax(uname, age) {
this.uname = uname;
this.age = age;
this.fn = function qwe() {
console.log(this.uname);
}
}
Ax.prototype.newfn = function () {
console.log(this.age);
}
let obj = mynew(Ax, 'lc', 18)
console.log(obj);
obj.fn()
obj.newfn()
function Qx() {
return function () {
console.log(123);
}
}
let obj2 = mynew(Qx)
console.log(obj2);
封装工具类Cache.js,可以对LocalStorage和SessionStorage操作(CRUD)
//用函数封装
function Cache(isLocal = true) {
this.storage = isLocal ? localStorage : sessionStorage
}
Cache.prototype.setItem = function (key, value) {
value ? this.storage.setItem(key, JSON.stringify(value)) : null
}
Cache.prototype.getItem = function (key) {
let value = this.storage.getItem(key)
return value ? JSON.parse(value) : null
}
Cache.prototype.removeItem = function (key) {
this.storage.removeItem(key)
}
Cache.prototype.clear = function () {
this.storage.clear()
}
Cache.prototype.length = function () {
return this.storage.length
}
let localCache = new Cache();
let sessionCache = new Cache(false);
//用class封装
// class Cache {
// constructor(isLocal = true) {
// this.storage = isLocal ? localStorage : sessionStorage;
// }
// setItem(key, value) {
// if (value) {
// this.storage.setItem(key, JSON.stringify(value))
// }
// }
// getItem(key) {
// let value = this.storage.getItem(key)
// if (value) {
// value = JSON.parse(value)
// return value
// }
// }
// removeItem(key) {
// this.storage.removeItem(key)
// }
// clear() {
// this.storage.clear()
// }
// length() {
// return this.storage.length
// }
// }
// let localCache = new Cache();
// let sessionCache = new Cache(false);
//检测
// localStorage.setItem("name", "wc");
// localStorage.setItem("obj", { a: 1 });
// // // 只传递了key,没有传递value
// localStorage.getItem("name")
// console.log(localStorage.getItem("name"));
// localStorage.getItem("address")
// console.log(localStorage.length);
console.log(localCache);
localCache.setItem("name", "wc");
localCache.setItem("obj", { a: 1 });
// // 只传递了key,没有传递value
localCache.setItem("obj2");
localCache.getItem("name")
console.log(localCache.getItem("name"));
localCache.getItem("address")
console.log(localCache.length());
// sessionCache.setItem("name", "wc");
// sessionCache.setItem("obj", { a: 1 });
// // 只传递了key,没有传递value
// sessionCache.setItem("obj");
// sessionCache.getItem("name")
// sessionCache.getItem("address")
// console.log(sessionCache.length());
myCall
//封装
(function () {
function mycall(context, ...args) {
// let arr = [...arguments]
context = context ? Object(context) : window
context.fn = this
if (!args) {
return context.fn()
}
let res = context.fn(...args)
delete context.fn
return res
}
Function.prototype.mycall = mycall;
})();
//测试
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
let obj = { name: "wc" };
// let res = fn.call(obj, 1, 2);
let res = fn.mycall(obj, 1, 2);
console.log(res);
myApply
//封装
(function () {
function myapply(context, arr) {
// let arr = [...arguments]
context = context ? Object(context) : window
context.fn = this
if (!arr) {
return context.fn()
}
let res = context.fn(...arr)
delete context.fn
return res
}
Function.prototype.myapply = myapply;
})();
//测试
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
let obj = { name: "wc" };
// let res = fn.apply(obj,[1, 2]);
let res = fn.myapply(obj, [1, 2]);
console.log(res);
封装检测数据类型的方法
// function checkType(data) {
// let type = Object.prototype.toString.call(data)
// return type.slice(8, -1).toLowerCase()
// }
let checkType = data => Object.prototype.toString.call(data).slice(8, -1).toLowerCase()
let type = checkType([])
console.log(type);
myMap
//callback回调函数
Array.prototype.mymap = function (cb) {
let newarr = []
for (let i = 0; i < this.length; i++) {
newarr.push(cb(this[i], i))
}
return newarr
}
let arr = [1, 2, 3]
let newarr = arr.mymap(function (item, idex) {
return item * item
})
console.log(newarr);
myFind
let arr = [1, 2, 5, 6, 8, 9, 9, 6, 5]
Array.prototype.myfind = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i,this))
return this[i]
}
}
let res = arr.myfind(function (item) {
return item > 5
})
console.log(res);
myFilter
let arr = [1, 2, 5, 6, 8, 9, 9, 6, 5]
Array.prototype.myfilter = function (cb, obj) {
let newarr = []
for (let i = 0; i < this.length; i++) {
(obj ? cb.bind(obj)(this[i], i, this) : cb(this[i], i, this)) && newarr.push(this[i])
}
return newarr
}
let res = arr.myfilter(function (item, i) {
console.log(this);
return item > 2
}, [8, 5])
console.log(res);