个人主页:学习前端的小z
个人专栏:JavaScript 精粹
本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论!
💯对象 Object
什么是对象? 咱们要说的对象可不是
女朋友
, 在这个世界上 任何具体事物都可以看做对象
因为他们都有自己的特征、行为。
车子 手机 猫 是对象吗? 这些都是一类事物, 只有具体的事物才是对象 , 比如我家的小黄猫。 小黄猫 特征: 颜色:黄色 年龄:1岁 体重:5kg 最爱:小鱼干 名字: 橘子 行为: 吃饭 睡觉 伸懒腰 喵喵喵
JavaScript是一门基于对象的语言。
javascript中 我们称Object为 对象 对象的概念也分广义和狭义:广义上javascript中处处是对象,狭义指的是我们通过{}字面量创建的对象。
JavaScript的对象是无序属性的集合。 其属性可以包含基本值、对象或函数。对象就是一组没有顺序的值。我们可以把JavaScript中的对象想象成键值对,其中值可以是数据和函数。 对象的行为和特征 特征---属性 行为---方法 事物的特征在对象中用属性来表示。 事物的行为在对象中用方法来表示。
🎲1 字面量
字面量(literal)是用于表达源代码中一个固定值的表示法(notation).
几乎所有计算机编程语言都具有对基本值的字面量表示, 诸如: 整数, 浮点数以及字符串; 而有很多也对布尔类型和字符类型的值也支持字面量表示; 还有一些甚至对枚举类型的元素以及像数组, 记录和对象等复合类型的值也支持字面量表示法.
字面量(literal),在高级语言中 我们可以通过更直观更高效的方式直接赋予变量 具体
值
, 当需要使用值得时候 才会去根据值得类型和内容进行包装解析; 先存储 后解释 。
🎀1.1 JavaScript中的字面量类型
1. 字符串字面量(String Literal)
var str = '粥老师'; //张晓华 就是字符串字面量
2.数组字面量(array literal)
var arr = [1,2,3,4,5]; //[1,2,3,4,5] 就是数组字面量
3.对象字面量(object literal)
var obj = { name:'橘子', age: 1, favorite: '小鱼干' } /* { name:'橘子', age: 1, favorite: '小鱼干' } 就是对象字面量 */
4.函数字面量(function literal)
var fn = function(){ alert('你好'); } /* function(){ alert('你好'); } 就是函数字面量 */
🎀1.2 JavaScript中的内置对象
JavaScript中的对象分为3种:内置对象、浏览器对象、自定义对象
javascript是基于对象的语言, javascript不可以自己创建一个原生对象,只能访问内置对象属性和调用已有的内置对象方法。但是可以通过基于Object创建狭义概念上的
对象
。
Number //数字 Array //数组 Boolean // 布尔 String //字符串 Object //对象 Function //函数 Date //时间 Math //数学 Null //空 RegExp //正则对象
🎀1.3 创建对象方式(自定义)
创建对象有三种基础方式: 对象字面量 原生对象实例化 自定义构造函数
- 字面量对象
var myCat = { name: '橘子', color: 'orange', age: 1, favorite: '小鱼干', speak: function(){ console.log('喵~~喵~喵~~~'); } }
- 原生对象实例化 new Object()
var myCat = new Object(); myCat.name = '橘子'; myCat.color = 'orange'; myCat.age = 1; myCat.favorite = '小鱼干'; myCat.speak = function(){ console.log('喵~~喵~喵~~~'); }
- 工厂函数创建对象
function createCat(name,age,color){ var cat = new Object(); cat.name = name; cat.color = color; cat.age = age; cat.favorite = '小鱼干'; cat.speak = function(){ console.log('喵~~喵~喵~~~'); } return cat; } var myCat = createCat('橘子',1,'orange');
- 自定义构造函数
function Cat(name,age,color,favorite){ this.name = name; this.age = age; this.color = color; this.favorite = favorite; this.speak = function(){ console.log('喵~~喵~喵~~~'); } } var myCat = new Cat('橘子',1,'orange','小鱼干');
🎲2 属性、方法
如果一个变量属于一个对象所有,那么该变量就可以称之为该对象的一个属性,属性一般是名词,用来描述事物的特征 如果一个函数属于一个对象所有,那么该函数就可以称之为该对象的一个方法,方法是动词,描述事物的行为和功能
🎲3 instanceof
在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。例如:
var oStringObject = new String("hello world"); console.log(oStringObject instanceof String); // 输出 "true"
🎲4 构造函数
面向对象编程’的第一步,就是要生成对象。而js中面向对象编程是基于构造函数(constructor)和原型链(prototype)的。
前面说过,“对象”是单个实物的抽象。通常需要一个模板,表示某一类实物的共同特征,然后“对象”根据这个模板生成。
js语言中使用构造函数(constructor)作为对象的模板。所谓构造函数,就是提供一个生成对象的模板,并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。
function Person(name,age,sex){ //Person就是构造函数 this.name = name; this.age = age; this.sex = sex; this.speak = function(){ console.log('我叫' + this.name + ',今年:' + this.age + '岁,性别:' + this.sex); } }
a:构造函数的函数名的第一个字母通常大写。 b:函数体内使用this关键字,代表所要生成的对象实例。 c:生成对象的时候,必须使用new命令来调用构造函数。
🎲5 new关键字
构造函数 ,是一种特殊的函数。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
- 构造函数用于创建一类对象,首字母要大写。
- 构造函数要和new一起使用才有意义。
new在执行时会做四件事情
1 创建一个空对象,作为将要返回的对象实例。 2 将空对象的原型指向了构造函数的prototype属性。 3 将空对象赋值给构造函数内部的this关键字。 4 开始执行构造函数内部的代码。
🎲6 this详解
JavaScript中的this指向问题,有时候会让人难以捉摸,随着学习的深入,我们可以逐渐了解 现在我们需要掌握函数内部的this几个特点 1. 函数在定义的时候this是不确定的,只有在调用的时候才可以确定 2. 一般函数直接执行,内部this指向全局window 3. 函数作为一个对象的方法,被该对象所调用,那么this指向的是该对象 4. 构造函数中的this其实是一个隐式对象,类似一个初始化的模型,所有方法和属性都挂载到了这个隐式对象身上,后续通过new关键字来调用,从而实现实例化
🎲7 对象的使用
🎀7.1 遍历对象的属性
通过for…in语法可以遍历一个对象
var obj = {}; obj.name = 'object'; obj['name'] = 'object'; var key = 'name'; console.log(obj[key]); for (var i = 0; i < 10; i++) { obj[i] = i * 2; } for(var key in obj) { console.log(key + "==" + obj[key]); }
🎀7.2 删除对象的属性
function fun() { this.name = 'mm'; } var obj = new fun(); console.log(obj.name); // mm delete obj.name; console.log(obj.name); // undefined
🎲8 JSON格式对象
JSON即Javascript对象表示方法 (Javascript Object Notation) ,也就是通过字面量来表示一个对象:
JSON 英文全称 JavaScript Object Notation JSON 是一种轻量级的数据交换格式。 它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集
🎀8.1 JSON 语法:
数据使用键名/值对表示,键名是字符串,值没有限定; 例如 “language”:”Java” 每个数据之间由逗号分隔; 使用大括号保存对象,对象可以包含若干个数据; 使用方括号保存数组,数组值使用“,”分割; JSON数据使用’”键名”:”值”’的形式,其中键名要求是字符串,而值 可以是以下任意类型: 1. 数值(整数,浮点数) 2. 字符串(在双引号中) 3. 逻辑值(true/false) 4. 数组(在方括号中) 5. 对象(在花括号中)
🎀8.2 JSON 示例:
var nodeDate = { "NodeName" : "P", "NodeType" : "Node.ELEMENT_NODE", "NodeId" : "", "NodeClassName" : "des", "NodeStyle": "text-align:center;color:blue;font-size:22px;text-indent:2em;", "NodeContent":"哈哈哈哈", "NodeChildElement" : [] }
var studentList = [ { "name": "张三", "age": 18, "sex": 1 }, { "name": "李思", "age": 19, "sex": 0 }, { "name": "王武", "age": 98, "sex": 1 }, { "name": "赵柳", "age": 77, "sex": 0 } ]
var chinaMap = [ { "code": "33", "name": "广东省", "children": [ { "code": "3301", "name": "广州市" }, { "code": "3302", "name": "深圳市" }, { "code": "3303", "name": "潮汕市" } ] }, { "code": "32", "name": "福建省", "children": [ { "code": "3201", "name": "福州市" }, { "code": "3202", "name": "宁德市" }, { "code": "3203", "name": "厦门市" } ] } ]
🎀8.3 JSON方法API:
🎏8.3.1 JSON.parse() 反序列化
将JSON数据解析为Javascript对象
方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。
var json = '{"result":true, "count":42}'; var obj = JSON.parse(json); console.log(obj.count); //42 console.log(obj.result); // true
参数
JSON.parse(text[, reviver]) text 要被解析成 JavaScript 值的字符串 reviver 可选 转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前。
返回值
Object 类型, 对应给定 JSON 文本的对象/值。
异常
若传入的字符串不符合 JSON 规范,则会抛出 SyntaxError 异常
🎏8.3.2 JSON.stringify() 序列化
方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
console.log(JSON.stringify({ x: 5, y: 6 })); // "{"x":5,"y":6}" console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)])); //"[3,"false",false]" console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] })); // "{"x":[10,null,null,null]}" console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5))); // ""2006-01-02T15:04:05.000Z""
参数
JSON.stringify(value[, replacer [, space]]) value 将要序列化成 一个 JSON 字符串的值。 replacer 可选 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。 space 可选 指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
返回值
一个表示给定值的JSON字符串。
异常
当发现循环引用时,抛出类型错误TypeError(“循环对象值”)异常。 当试图将BigInt (BigInt 为javascript中Number能表示的最大数 2^53 - 1 )值字符串化时,会抛出类型错误 TypeError(“BigInt值不能在JSON中序列化”)。
💯执行上下文
🎲1 什么是执行上下文
简而言之,执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行
🎲2. 执行上下文的类型
执行上下文总共有三种类型:
- 全局执行上下文:这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
- 函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
- Eval 函数执行上下文: 运行在 eval 函数中的代码也获得了自己的执行上下文,但由于eval是魔鬼 我们一般不通过eval进行开发操作。
🎲3 执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段,本文重点介绍创建阶段。
🎀3.1 创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
- 创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明。下文会详细说明。
- 创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
- 确定 this 指向:包括多种情况,下文会详细说明
在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为 undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。
另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出 this arguments 和函数的参数。
🎀3.2 执行阶段
执行变量赋值、代码执行
🎀3.3 回收阶段
执行上下文出栈等待虚拟机回收执行上下文
🎲4 变量提升和 this 指向的细节
🎀4.1 变量声明提升
大部分编程语言都是先声明变量再使用,但在 JS 中,事情有些不一样:
console.log(a); // undefined var a = 10;
上述代码正常输出undefined
而不是报错Uncaught ReferenceError: a is not defined
,这是因为声明提升(hoisting),相当于如下代码:
var a; //声明 默认值是undefined “准备工作” console.log(a); a = 10; //赋值
🎀4.2 函数声明提升
我们都知道,创建一个函数的方法有两种,一种是通过函数声明function foo(){}
另一种是通过函数表达式var foo = function(){}
,那这两种在函数提升有什么区别呢?
console.log(f1); // function f1(){} function f1() {} // 函数声明 console.log(f2); // undefined var f2 = function() {}; // 函数表达式
接下来我们通过一个例子来说明这个问题:
function test() { foo(); // 未捕获类型错误“foo不是函数” bar(); // "this will run!" var foo = function() { console.log("this won't run!"); }; function bar() { ale console.logrt("this will run!"); } } test();
在上面的例子中,foo()调用的时候报错了,而 bar 能够正常调用。
我们前面说过变量和函数都会上升,遇到函数表达式 var foo = function(){}
时,首先会将var foo
上升到函数体顶部,然而此时的 foo 的值为 undefined,所以执行foo()
报错。
而对于函数bar()
, 则是提升了整个函数,所以bar()
才能够顺利执行。
有个细节必须注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。
alert(a); //输出:function a(){ alert('我是函数') } function a() { alert("我是函数"); } // var a = "我是变量"; alert(a); //输出:'我是变量'
function 声明的优先级比 var 声明高,也就意味着当两个同名变量同时被 function 和 var 声明时,function 声明会覆盖 var 声明
这代码等效于:
function a() { alert("我是函数"); } var a; //hoisting alert(a); //输出:function a(){ alert('我是函数') } a = "我是变量"; //赋值 alert(a); //输出:'我是变量'
最后我们看个复杂点的例子:
function test(arg) { // 1. 形参 arg 是 "hi" // 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function console.log(arg); var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行 function arg() { console.log("hello world"); } console.log(arg); } test("hi"); /* 输出: function arg(){ console.log('hello world') } hello */
这是因为当函数执行的时候,首先会形成一个新的私有的作用域,然后依次按照如下的步骤执行:
- 如果有形参,先给形参赋值
- 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值
- 私有作用域中的代码从上到下执行
🎀4.3. 确定 this 的指向
先搞明白一个很重要的概念 —— this 的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为 this 是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子:
// 情况1 function foo() { console.log(this.a) //1 } var a = 1 foo() // 情况2 function fn(){ console.log(this); } var obj={fn:fn}; obj.fn(); //this->obj // 情况3 function CreateJsPerson(name,age){ //this是当前类的一个实例p1 this.name=name; //=>p1.name=name this.age=age; //=>p1.age=age } var p1=new CreateJsPerson("小z",18); //情况4 var obj = { name:'小z', showName:function(){ console.log(this.name); // 小z this => obj (function(){ console.log(this.name); //undefined this=>window })(); } } obj.showName(); </script>
接下来我们逐一解释上面几种情况
- 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
- 对于 obj.foo() 来说,我们只需要记住,谁调用了函数, this指向谁,所以在这个场景下 foo 函数中的 this 就是 obj 对象
- 在构造函数模式中,类中(函数体中)出现的 this.xxx=xxx 中的 this 是当前类的一个实例
- IIFE匿名函数自调用时 因为没有明确调用主体 this指向 window。
🎲5 执行上下文栈(Execution Context Stack)
函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?
JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
从上面的流程图,我们需要记住几个关键点:
- JavaScript 执行在单线程上,所有的代码都是排队执行。
- 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
- 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
- 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈。
我们再来看个例子:
var color = "blue"; function changeColor() { var anotherColor = "red"; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors(); } changeColor();
上述代码运行按照如下步骤:
- 当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈
- 调用 changeColor 函数时,此时 changeColor 函数内部代码还未执行,js 执行引擎立即创建一个 changeColor 的执行上下文(简称 EC),然后把这执行上下文压入到执行栈(简称 ECStack)中。
- 执行 changeColor 函数过程中,调用 swapColors 函数,同样地,swapColors 函数执行之前也创建了一个 swapColors 的执行上下文,并压入到执行栈中。
- swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。
- changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。
🎲6 包装对象
基础类型undefined、null、boolean、number、string和symbol 都是非引用类型(非对象)
之前我们学习了对象 了解到 对象具有属性和方法 也学习了字面量值 我们一起来看一个案例
var str = '你好'; var arr = str.split(''); console.log(arr); // ["你", "好"] • 1 • 2 • 3 • 4
问题:
一个以string基础类型的字面量为值的变量str 是如何能够通过 . 操作执行方法的呢? str到底是对象 还是 字符串呢?
var str = '你好'; //var newStr = new String('你好'); 包装 实例化对象 var arr = str.split(''); //var arr = newStr.split(''); 通过包装对象调用对象方法 // newStr = null; 销毁包装对象 console.log(arr); // ["你", "好"]
🎲7 常用内置对象方法
javascript内置对象都提供了基本方法, 学习方法需要注意 参数 返回值 是否改变原对象 三个点。