JS函数简介
使用function关键字定义函数对象,函数的typeof类型也是function
1 | function hello(){ |
使用函数表达式创建匿名函数,即将匿名函数赋值给一个变量
1 | var fun = function(){ |
函数是对象,也可以new出来,但一般不用这种形式,Function里面的参数都必须是字符串格式
1 | var fun = new Function('a', 'b', 'console.log(a + b);'); |
使用return终止函数或返回一些数据,默认返回undefined
函数名就是整个函数,JS加载的时候,只加载函数名,不加载函数体
1 | function fun(){ |
函数的调用
1、调用函数可以函数名调用,可以用call()方法
1 | function fun(){ console.log('hello'); } |
2、在对象中保存多个函数,通过对象的方法来调用,函数作为对象的属性保存,这个函数就是对象的方法
1 | var obj = { |
3、立即执行函数,在定义后立即执行
1 | (function() { |
4、通过构造函数调用
1 | function Fun() { |
5、绑定事件函数,触发事件后立即执行的函数
1 | var btn = document.getElementById('btn'); |
6、定时函数
1 | setInterval(function () { |
形参和实参
形参:函数在定义时要求传入的一些参数
实参:调用函数时传入的一些参数
实参将按顺序传递给函数中对应的形参
1 | function fun(a, b){//该函数需要两个形参 |
实参和形参,数量可以不同,调用函数时,解析器不会检查实参的数量。
实参多余形参:忽略多余的实参
实参少余形参:没有接收到值的形参默认为undefined,undefined参与运算为NaN
1 | function fun(a, b){ console.log(a + b); }; |
调用函数时,解析器也不会检查实参类型,实参可以是任意数据类型
类数组对象arguments
在调用函数时,浏览器每次都会传递进两个隐含的参数:(箭头函数没有自己的this和arguments。)
- 函数的上下文对象 this
- 封装实参的对象 arguments
arguments是一个类数组对象(伪数组),不是一个真正的数组,除了length属性、可以通过索引获取元素之外没有任何数组属性和方法。
Array.from()
能将伪数组转为真数组
1 | function fun(a, b){ |
案例:将传入的实参进行求和,无论实参的个数有多少:
1 | function fun() { |
arguments.callee返回正在执行的函数
1 | function fun() { |
通过arguments修改传入的实参
1 | function fun(a, b){ |
作用域
变量或函数的作用范围。作用域在函数定义时,就确定。
全局作用域:作用于整个script标签内,或作用于一个独立的JS文件
函数作用域:作用于函数部
块级作用域:ES6新增,作用于一个区块{}内
在全局作用域中有一个全局对象 window(和微软没有半毛钱关系),它代表的是浏览器的窗口,由浏览器创建。
创建的全局变量都会作为 window 对象的属性保存。
创建的全局函数都会作为 window 对象的方法保存。
变量如果未经声明(没有var、let、const)就赋值,是全局变量
1 | var a = 1; |
连续赋值时,除了第一个声明的变量之外,后面的变量都是全局变量
1 | function fun(){ |
变量、函数提升
全局作用域的预处理:JS在解析代码之前,将当前 JS 代码中所有变量的定义和函数的定义,放到所有代码的最前面,先进行声明但不进行赋值,默认都是undefined。
任何变量,如果未经声明(没有var、let、const)就赋值,此变量是属于 window 的属性,而且不会做变量提升
1 | console.log(a);//undefined |
其它作用域也有类似的预处理,这就是变量提升,即将变量声明提升到它所在作用域的最开始的部分。
函数的声明提前(函数提升):
使用函数声明的形式创建的函数function fun(){},会被函数提升,可以提前调用函数,能正常执行。
使用函数表达式创建的函数var fun = function(){},不会被函数提升,但属于变量提升,不能提前调用,会被认为不是一个函数。
函数的形参就相当于在函数作用域中声明了变量。
1 | console.log(fun1(1, 2));// 3,提前调用可以正常执行函数 |
函数提升优先于变量提升
1 | fun(); // 先函数提升,所以输出B |
函数的嵌套:会出现作用域链,在嵌套函数中,变量会从内到外逐层寻找它的定义(查找时,采用就近原则)。
1 | var a = 0; |
this指向
this
即‘当前’,指的是函数运行时所在的环境,它永远指向函数的真实调用者,如果没有调用者,就指向全局对象window。
1 | var a = -1; |
this在函数调用时绑定,函数执行时会创建一个活动记录,这个记录里包含了该函数中定义的参数,也包含函数在哪里被调用(调用栈),this就是其中的一个属性。
绑定规则
- 默认绑定:把this绑定到全局对象window,以函数的形式而非对象的方法(包括普通函数、定时器函数、立即执行函数)调用时。
- 对象中的this:隐式绑定(上下文绑定)对象内部方法的this指向调用此方法的对象,谁调用就指向谁
- 构造函数中的this:构造函数中的this指向构造函数下创建的实例对象,构造函数返回创建的对象。
- 以事件绑定函数的形式调用时,this指向绑定事件的对象
- 箭头函数中的this:指向函数作用域所用的对象
1、默认绑定:把this绑定到全局对象window,以函数的形式而非对象的方法(包括普通函数、定时器函数、立即执行函数)调用时。
1 | var a = 0; |
2、对象中的this:隐式绑定(上下文绑定)对象内部方法的this指向调用此方法的对象,谁调用就指向谁
1 | var a = 0; |
多层对象时,内部方法this指向离被调用函数最近的对象
1 | var a = 0; |
上面代码中,若obj2中没有a属性,this.a是undefined,即无论对象嵌套多少层,this只会指向直接调用该函数的对象(离被调用函数最近的对象)
this永远指向函数的真实调用者
1 | var a = 0; |
隐式绑定的丢失:通过赋值,导致隐式绑定的丢失。
1 | var a = 0; |
上面代码中,obj的fun属性引用了fun函数的引用内存地址,在obj.fun()调用函数时,fun函数中的this会动态绑定对象(当前函数的直接调用者,即obj对象),将obj.fun的引用地址赋值给了foo那么foo也引用了fun函数的引用内存地址,使用foo()时,fun函数中的this也会动态绑定对象(当前函数的直接调用者,即window对象)。
3、构造函数中的this:构造函数中的this指向构造函数下创建的实例对象,构造函数返回创建的对象。
1 | function Fun(name, age){ |
4、以事件绑定函数的形式调用时,this指向绑定事件的对象
1 | var btn = document.getElementById('btn'); |
5、箭头函数中的this:指向函数作用域所用的对象
箭头函数的重要特征:箭头函数没有自己的this和arguments,但它会继承自己定义时所处的外层执行环境的this指向,指向当前定义时所在的对象,call()、apply()、bind()等方法无法改变箭头函数继承的this指向。
简单地说,箭头函数会找它的上一级作用域。如果父级作用域还是箭头函数,就再往上找,一层层找,直到找到this指向的对象
1 | var obj = { |
多层对象时,仍然指向最外部对象定义时所在的环境
1 | var obj1 = { |
用构造函数创建对象,创建出来的对象中的箭头函数和普通函数,都指向构造函数创建出来的对象,但一个是在定义时指向obj对象,一个是在调用时指向obj对象
1 | function foo(){ |
普通函数中的箭头函数:
箭头函数的外层如果有普通函数,那么箭头函数的this就是这个外层的普通函数的this,箭头函数的外层如果没有普通函数,那么箭头函数的this就是全局变量window。
或者说,普通函数中的箭头函数的this被绑定到该函数执行的作用域上
1 | var obj={ |
1 | var birth = 2000 |
改变this指向的方法
JS在Function的porpertype属性上提供了3个方法来强行修改函数内部的this指向,不想改变this指向则传入null或this,这三个方法都不会改变原函数的指向(动态的)
call()
:传入多个参数,第一个是要修改的this的指向,剩下的会传给函数当参数,然后会执行这个函数apply()
:传入两个参数,第一个是要修改的this的指向,第二个是一个数组,它保存了要传入函数的多个参数,然后会执行这个函数bind()
:传入多个参数,第一个是要修改的this的指向,剩下的会传给函数当参数,不会执行这个函数,但会返回指定this和指定实参的原函数拷贝(一个改变了this指向和已经传入了参数的新函数)
1、call()
:传入多个参数,第一个是要修改的this的指向,剩下的会传给函数当参数,然后会执行这个函数
1 | fun.call(想要将this指向的对象, 函数实参1, 函数实参2); |
通过call()调用函数,不改变指向
1 | var a = 1; |
通过call()改变this指向
1 | var a = 1; |
2、apply()
:传入两个参数,第一个是要修改的this的指向,第二个是一个数组,它保存了要传入函数的多个参数,然后会执行这个函数
1 | fun.apply(想要将this指向的对象, [函数实参1, 函数实参2]); |
1 | var obj = { |
通过apply()求数组的最大值:
数组本身没有求最大值的方法,但是数学对象中有Math.max(数字1,数字2…),apply可以传入一个数组作为其参数,不改变其指向即可
1 | var arr = [4, 2, 6, 5]; |
3、bind()
:传入多个参数,第一个是要修改的this的指向,剩下的会传给函数当参数,不会执行这个函数,但会返回指定this和指定实参的原函数拷贝
1 | var a = 1; |
内存回收机制
JS具有内存自动回收机制,周期性的找出不再继续使用的变量,然后释放其占用的内存。
在闭包中,如果引用了外部的变量,则无法进行释放和回收,造成内存泄漏
常见内存泄漏:全局变量、闭包、Dom元素的引用、定时器
IE回收不了闭包里面引用的变量,但2023年了,主流浏览器都能回收闭包内不再使用的变量,js闭包测试—司徒正美
1 | for(let i = 0; i < 5; i++) { |
闭包
如果外部作用域有权访问另外一个函数内部的局部变量时,那就产生了闭包。这个内部函数称之为闭包函数
函数和函数内部能访问到的变量的总和,就是一个闭包。
闭包是JS函数作用域的副产品,因为JS的函数内部可以使用函数外部的变量。
闭包的生命周期:
产生:内部函数被声明时就产生了。
死亡:嵌套的内部函数成为垃圾对象时。(比如fun = null,就可以让 fun 成为垃圾对象)
闭包的作用:将函数内部的变量(局部变量)能被外部访问,隐藏一些变量,延长局部变量的生命周期
1 | function fun1() { |
上面的代码中,通过一些操作,让外部作用域(即全局作用域)有权访问函数fun1中的局部变量,在fun1中就产生了闭包,函数fun1是闭包函数,闭包是fun2和fun2所能访问到的变量a。
延长局部变量的生命周期:
1 | // fun1执行完一次,局部变量就立即销毁,下次调用a还是0; |
隐藏一些变量:
打游戏时通常有血条和蓝条,我们当然不希望用户window.blood就能修改血量。将血量变量blood放进匿名函数中,在函数内部去声明一些修改血量的代码再赋给全局变量,就能在匿名函数外部通过这些函数去修改血量,但不能直接访问血量。
1 | !function(){ //匿名函数 |
封装JS模块:定义具有特定功能的JS模块,将所有的数据和功能都封装在一个函数内部,只向外暴露指定的对象或方法。模块的调用者,只能调用模块暴露的对象或方法来实现对应的功能
如果不想将这三个函数直接赋给全局变量,也可以让函数返回一个装有三个方法的对象
1 | function Blood(){ //匿名函数 |
面向对象概述
Java中已经学习过了什么是面向对象,这里不再扯概念。
JS是基于原型的面向对象,JS中的对象(Object)是依靠构造器(constructor)和原型(prototype)构造出来的
在ES6中,新引入了类(Class)和继承(Extends)来实现面向对象
面向对象的编程思想:对代码和数据进行封装,并以对象调用的方式,对外提供统一的调用接口
调用对象的属性:obj.name
或obj['name']
对象的创建
创建对象:
1、对象字面量{}
1 | var obj = { |
2、工厂模式 new Object() 大量创建同种对象
1 | function createPerson(name, age) { |
3、构造函数
1 | function Person(name, age) { |
构造函数
构造函数:是一种特殊的函数,主要用来创建和初始化对象,也就是为对象的成员变量赋初始值。
创建构造函数时,里面的属性和方法前必须加this,this就表示当前要构造的对象。
普通函数是直接调用,而构造函数需要使用 new 关键字来调用。
构造函数的执行流程:
- 立刻创建一个对象
- 将新建的对象设置为函数中this,使得在构造函数中可以使用this来引用新建的对象
- 遂行执行函数中的代码,给这个新对象添加属性和方法
- 将新建的对象作为返回值返回(构造函数中无需return)
1 | // 构造函数 |
静态成员和实例成员:
1、静态成员:构造函数本身上添加的成员,静态成员只能通过构造函数访问,不能通过对象访问
2、实例成员:构造函数内部通过this添加的成员,实例成员只能通过实例化的对象进行访问
1 | function Student(name) { |
类、实例
使用同一个构造函数创建的对象,都称为一类对象,也将构造函数称为类。通过一个构造函数创建的对象,称为该类的实例。
使用 instanceof 可以检查一个对象是否为一个类的实例。
1 | function Person() {} |
对象的基本操作
- 向对象中添加属性:
对象.属性名 = 属性值
- 获取对象中的属性:
对象.属性名
- 修改对象的属性值:
对象.属性名 = 新值
- 删除对象的属性:
delete 对象.属性名
- in 运算符:
属性名 in 对象
检查一个对象中是否含有指定的属性
1 | var obj = {};// 创建 |
遍历对象
遍历对象时,要根据对象的结构配合使用多种方法,通常还需要配合数组的遍历方法
1、for-in
遍历对象的属性,再使用对象[属性名]
获取属性值
for-of用于遍历数组的元素,直接获取元素。
1 | var obj = { |
1 | student |
2、Object.keys
返回对象自身属性名组成的数组,Object.values
返回对象自身属性值组成的数组
1 | var obj = { |
1 | student |
3、Object.entries()
返回Object.keys与Object.values的结合体,一个嵌套的数组,数组内包括了属性名与属性值,下标0存储属性名
1 | var obj = { |
1 | student |
4、Object.getOwnPropertyNames()
与Object.keys差不多,不同的是会返回对象的所有属性,包括了不可枚举属性,如数组对象的length
5、Object.getOwnPropertySymbols()
返回对象内的所有Symbol属性的数组,对象初始化的时候,内部不包含任何Symbol属性
对象访问器
JS提供了Getter(get关键字)和 Setter(set关键字) 来定义对象访问器(属性访问器)
1 | var obj = { |
使用 getter 和 setter 可以确保更好的数据质量,一些会随时间而变等的属性(如年龄),实际无需静态地存储在对象中,且可以对数据进行加工处理,类似数据库中的视图的功能
1 | var obj = { |
Object对象
JavaScript中的对象其实就是一组数据和功能的集合。
Object对象是所有对象的祖宗,其他对象都继承自Object,即其它对象都是Object的实例
每个Object类型的实例共有的属性和实例方法(定义在Object原型对象Object.prototype上的方法。可以被Object实例直接使用):
- constructor:保存用于创建当前对象的构造函数。
- __proto__:隐式原型,指向的Object原型对象(父对象,所有类型的对象都有这个属性)
hasOwnProperty()
:检测实例中是否有指定属性。isPrototypeOf()
:判断传入的对象是否是当前对象的原型propertyIsEnumerble()
:检查指定属性能否使用for-in来枚举遍历toLocaleString()
:返回对象的字符串表示toString()
:返回对象的字符串表示valueOf()
:返回对象本身
静态方法:直接定义在Object对象的方法,Object.
直接调用
控制对象状态的方法:
preventExtensions()
:防止对象扩展isExtensible()
:判断对象是否可扩展seal()
:禁止对象配置isSealed()
:判断一个对象是否可配置freeze()
:冻结一个对象isFrozen()
:判断一个对象是否被冻结
对象属性模型的相关方法
keys()
:返回对象自身属性名组成的数组getOwnPropertyNames()
:与keys()差不多,但返回对象所有属性的数组,包括了不可枚举属性,如数组对象的lengthgetOwnPropertyDescriptor()
:获取某个属性的描述对象,参数(对象,属性名的字符串)defineProperty()
:通过描述对象,定义或修改某个属性。给对象添加一个属性并指定该属性的配置defineProperties()
:通过描述对象,定义多个属性。hasOwn()
:判断是否为自身的属性
原型链相关方法
is()
:比较两个值是否严格相等,严格比较create()
:指定原型对象和属性,返回一个新的对象values()
:返回对象自身属性值组成的数组entries()
:返回一个数组,元素是对象自身的(不含继承的)所有可遍历属性的键值对数组fromEntries()
:将一个键值对数组转为对象。assign()
:对象的合并,复制一个或者多个对象来创建一个对象,浅拷贝,将源对象的所有可枚举的自身属性,复制到目标对象getPrototypeOf()
:获取对象的原型对象(Prototype对象)setPrototypeOf()
:设置对象的原型对象
Object.keys(obj).length
获取对象的长度
Object.prototype.toString.call()
可以在任意值(对象)上调用这个方法,以判断这个值的类型
1 | //[object Number]第一个值代表是对象,第二个值表示该值的构造函数即类型 |
JSON
JSON(JavaScript Object Notation),即JavaScript对象表示法,它是一种数据交换的文本格式,使用JS语法来描述数据对象,而不是一种编程语言。
大多数语言都支持对json的解析。JS中可以原生地把json转为object对象。
数据结构:Object、Array
基本类型:string,number,true,false,null(json无法表示undefined)
1 | {}//这是一个json |
对象和 json 没有长度,json.length 的打印结果是 undefined
使用for-in遍历json:
1 | //将json存在变量中 |
1 | student |
for-in获取json对象的属性:
1 | var json = { "name":"chuckle", "age":19 }; |
for-in获取json对象的属性的值:
1 | var json = { "name":"chuckle", "age":19 }; |
前端收到的api通常也是json格式,如一言api:
1 | { |
JSON.parse()
将数据转换为 JavaScript 对象JSON.stringify()
将 JavaScript 对象转换为字符串
Map对象
Map对象保存键值对,元素会保持其插入时的顺序。
Map的键可以是任意数据类型,包括函数、对象或任意基本类型。
在需要进行很多新增操作,且需要储存许多数据的时候,使用 Map 会更高效
1 | var m = new Map(); |
Object与Map增删改查基本操作。
1 | var o = {}; |
Map的键值对个数可以通过size属性获取
1 | var m = new Map(); |
Map的方法
基本方法:
get()
:获取元素set()
:设置元素has()
:检查是否有指定keyclear()
:清空mapdelete()
:删除指定元素
遍历方法:
keys()
:提取键并返回键的迭代器MapIterator对象values()
:提取值并返回值的迭代器MapIterator对象entries()
:提取键值对并返回取键值对的迭代器MapIterator对象forEach()
:传入回调函数(value, key)=>{}
1 | var m = new Map([ |
Set对象
Set是唯一值的集合,与map类似,map存放的是键值对,而set只存放唯一值。
创建set对象:
1 | var s = new Set(); |
set对象与数组也很像,可以互相转换
1 | var arr1 = [1, 2, 3]; |
可利用set值唯一的特性做数组去重、并集、交集、差集操作
1 | //1、去重 |
Set值个数可以通过size属性获取
1 | var s = new Set([1,2,3]); |
Set的方法
add()
:添加新元素delete()
:删除指定元素clear()
:清空所有元素has()
:判断是否存在某值forEach()
:遍历每个元素,传入回调函数keys()
:返回一个 Iterator 对象,这个对象以插入Set 对象的顺序包含了原 Set 对象里的每个元素values()
:同keys()entries()
:返回 [value, value] 形式的数组迭代器对象,value 是给定集合中的每个元素,迭代器对象元素的顺序即集合对象中元素插入的顺序
add()、delete()、clear()、has() :
1 | var s = new Set([1,2,3]); |
keys()、values() :返回一个 Iterator 对象,这个对象以插入Set 对象的顺序包含了原 Set 对象里的每个元素
1 | var s = new Set([1,2,3]); |
entries() :返回 [value, value] 形式的数组迭代器对象,value 是给定集合中的每个元素,迭代器对象元素的顺序即集合对象中元素插入的顺序
1 | var s = new Set([1,2,3]); |
forEach() :遍历每个元素,传入回调函数,参数:回调函数、thisArg执行回调函数时可以当作this来使用。
回调函数参数:值(key)、值(value)、set对象
1 | var s = new Set([1,2,3]); |
浅拷贝和深拷贝
浅拷贝:只拷贝最外面一层的数据;更深层次的对象,只拷贝引用,浅拷贝的时候,是属于传址,而非传值。
深拷贝:拷贝多层数据;每一层级别的数据都会拷贝,深拷贝会把对象里所有的数据重新复制到新的内存空间,是最彻底的拷贝。
区分深拷贝与浅拷贝:B复制了A,修改A,B也一样被修改是浅拷贝,B没变,是深拷贝。
1 | var a = [0,1,2,3]; |
通过Object.assign()
实现浅拷贝:
1 | var obj1 = { |
实现深拷贝
通过for-in
递归实现深拷贝,即递归遍历整个对象,找到简单值,将值复制
1 | var obj1 = { |
通过JSON对象的parse和stringify方法实现深拷贝
1 | function deepClone(obj){ |
数组等对象的slice()
不是完全的深拷贝,因为数组中存的是对象的引用地址,slice()只将地址拷贝了过去
1 | var a = [0, 1, [1, 1, 1], 2, 3]; |
迭代器Iterator
迭代以从一个数据集中按照一定的顺序,不断取出数据的过程。
迭代与遍历的区别:
- 迭代强调依次取数据的过程,不保证把所有的数据都取完
- 遍历强调的是要把所有的数据依次全部取出
迭代器是能调用next()
实现迭代的一种对象,该方法返回一个具有两个属性的对象(value:可迭代对象迭代至此的值,done:布尔,是否已经取出所有数据)
通过可迭代对象中的迭代器工厂函数Symbol.iterator
来生成迭代器。每次生存的迭代器之间互不干扰。
1 | var arr = [1, 2, 3, 4]; |
迭代器对象可作为可迭代对象,for-of遍历可迭代对象
1 | var arr = [1, 2, 3, 4]; |
如果可迭代对象在迭代期间被修改了,迭代器得到的结果也是修改后的。
1 | var arr = [1, 2, 3, 4]; |
当迭代到 done: true 时迭代器会处于一种完成但并不完成的状态,还能重复调用 next(),结果都是 { value: undefined, done: true }
正则表达式
正则表达式:用某种模式去匹配一类字符串的公式,正则表达式在线测试
正则表达式主体和修饰符:
1 | i:不区分大小写的匹配 |
1 | [abc] 匹配方括号之间的任何字符 |
1 | .(点号) 匹配单个字符,除了换行和行结束符 |
1 | \n 匹配换行符 |
1 | + 重复1次或更多次 |
RegExp对象
在js中,正则表达式也是对象,RegExp是一个预定义了属性和方法的正则表达式对象
1 | var re = /正则表达式主体/修饰符(可选);// re = /Hello/g |
RegExp对象的属性和方法:
- global: 判断是否设置了 “g” 修饰符
- ignoreCase:判断是否设置了 “i” 修饰符
- multiline:判断是否设置了 “m” 修饰符
- lastIndex:规定下次匹配的起始位置
- source:返回正则表达式的匹配模式
test()
:判断指定字符串是否符合正则规则,返回布尔exec()
:返回一个数组,存放正则匹配的结果。无匹配返回 nulltoString()
:返回正则表达式的字符串。
exec()
在 regexp 的属性 lastIndex 指定的字符处开始检索字符串,当它找到了与表达式相匹配的文本时,在匹配之后,它将把 regexp 的 lastIndex 属性设置为匹配文本后的第一个字符所在位置(调用test()也会改变lastIndex),可以通过反复地调用 exec() 方法来遍历字符串中的所有匹配文本,当 exec() 再也找不到匹配的文本时,它将返回 null,并且把属性 lastIndex 重置为 0
返回值:匹配到的文本的数组,数组有四个属性,index 匹配文本第一个字符的位置,input 需匹配的原字符串,groups 当初中命名的分组时匹配到的分组对象
1 | var str="Hello world! Hello china!"; |
检查一个字符串是否是一个合法手机号
以1开头(^1 表示1开头)
第二位是3~9之间任意数字[3-9]
三位以后任意9位数字[0-9]{9}重复9次的0-9
1 | var str = "15123456789"; |
判断字符串是否为电子邮件
1 | var str = "916017604@qq.com" |
支持正则表达式的 String 对象的方法:
search()
使用表达式来搜索匹配,然后返回匹配的位置replace()
返回模式被替换处修改后的字符串,不改变原字符串,返回替换后的字符串match()
返回匹配到的字符串的数组
1 | var str = "你好世界"; |
1 | var str = "我的电话号码是15123456789"; |
1 | var str = "http://127.0.0.1:4000/"; |
原型与原型链
省流:
原型其实就是一个对象,实例继承原型对象的属性,通过继承的这种方式,new出来的实例也有了这个属性
对象的构造函数有一个 prototype 的属性,通过这个属性就能访问到原型
对象有一个 __proto__ 属性,指向构造函数的 prototype 的属性,所以也可以访问到原型
构造函数也有 __proto__ 属性,也是对象,因为所有函数都是 Function 构造的,所以都等于 Function.prototype
原型也是对象,也有 __proto__ 属性,通常浏览器称之为 [[Prototype]] ,它指向原型的原型,像单链表的next指针一样,构成原型链,直到Object构造函数,因为 Object 对象的原型 Object.prototype.proto 为空null没有下一个原型了,Object.prototype是原型的终点
原型与原型链其实是为了实现继承,原型就像java中的类,由构造函数构造的对象都继承了这个构造函数原型的属性和方法,当然,每个对象后续都可以添加自己的属性和方法来覆盖继承自原型的属性和方法。
原型链中每个节点是构造函数的原型,每个节点有一个循环:构造函数的 prototype 属性指向它的原型,原型的 constructor 属性指向它的构造函数
普通对象也可以利用 Object.create() 作为新生成对象的原型。
原型
每个 JS 对象一定对应一个原型对象,并从这个原型对象继承属性和方法。
对象只有隐式原型 __proto__
函数有隐式原型 __proto__ ,还有显式原型 prototype
对象的 __proto__ 指向 其构造函数.prototype,或者说 对象.__proto__ 等于 其构造函数.prototype 保存的东西是一样的
函数的 __proto__ 都指向Function.prototype,因为所有函数都是由Function构造的,值为 ƒ(){[native code]}
函数的 prototype 指向一个对象,就是通常所说的原型对象,一定有两个属性,constructor 指向构造函数本身,[[Prototype]]就是__proto__,它连接起一个个原型,构成原型链,Object 对象的原型 Object.prototype.proto 为空null
两种获取对象隐式原型的方式:对象的__proto__ 属性(非标准),Object.getPrototypeOf()
方法(标准)
获取显式原型:构造函数.prototype
1 | var obj = new Object(); |
只有一个Object对象不够直观,新建一个构造函数,new一个Person对象
1 | function Person() { |
下面的输出就是Person对象的原型,可以发现原型上并没有a属性,在构造函数中通过this直接定义实例成员,会作为实例对象的属性,而不是出现在原型上再被对象继承。
1 | {constructor: ƒ} |
这个原型上只有两个属性,我们可以在原型上添加属性和方法,所有对象都会继承这些属性和方法,哪怕是在这之前已经实例化的对象。
1 | function Person() { |
可以看到原型上出现了a属性,值为0
1 | {a: 0, constructor: ƒ} |
试着在实例化对象中使用继承来的a、b属性
1 | function Person() { |
person.a 输出 1,访问的是实例成员a,也就是说对象的属性可以覆盖继承来的同名属性
1 | 1 |
再看原型中的的 constructor 属性,它指向该原型对应的构造函数,可以在控制台中展开
构成了一个循环:构造函数的 prototype 属性指向它的原型,原型的 constructor 属性指向它的构造函数
原型链
原型是原型链上的节点,各个原型通过 __proto__ 相连接,对象可以继承原型链上从Object构造函数开始至该对象构造函数的所有原型的属性和方法,即继承了其所有父级的所有属性和方法,且逐层可以覆盖
制造原型链:
直接操作prototype属性:
1 | function Parent(){} |
可以在控制台展开 [[Prototype]] 属性来查看原型链
通过 Object.create()
建立原型链,在Vue源码中 Object.create() 的使用频率非常高
Object.create()
用于创建一个新对象,使用现有的对象来作为新创建对象的原型
原型和普通对象都有proto和constructor属性,且作用都一样,利用Object.create()可以将一个普通对象作为新对象的原型
1 | function Parent(){ |
将原型与构造函数的循环、原型与原型的链、构造函数的实例组成一张图: