JavaScript高级笔记-coderwhy版本(四)https://developer.aliyun.com/article/1469640
理解组合函数
- 组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:
- 比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的;
- 那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复
- 那么是否可以将这两个函数组合起来,自动依次调用呢?
- 这个过程就是对函数的组合,我们称之为 组合函数(Compose Function);
function double(num){ return num*2 } function square(num){ return num ** 2//平方 } var count = 10 var result = square(double(count)) console.log(result); //如何将double和square结合起来,实现简单的组合函数 function composeFn(m,n){ return function(count){ return n(m(count)) } } var newFn = composeFn(double,square) console.log(newFn(10));
通用组合函数的实现
- 刚才我们实现的compose函数比较简单,我们需要考虑更加复杂的情况:比如传入了更多的函数,在调用 compose函数时,传入了更多的参数:
function hyCompose(...fns){ var length = fns.length for(var i = 0;i < length;i++){ if(typeof fns[i] !== 'function'){ throw new TypeError('要求都是函数类型')//new出一个异常的错误,抛出异常 } } function compose(...args){ var index = 0 //fns[index].apply(this,args):取出来fns第一个函数进行apply调用,并将args参数都传递进去。注意,我们是直接使用fns而不是...fns哦 var result = length ? fns[index].apply(this,args) : args while(++index < length){ result = fns[index].call(this,result) } return result } return compose } function double(m){ return m*2 } function square(n){ return n ** 2 } var newFn = hyCompose(double,square) console.log(newFn(30));
08_基于对象的封装、原型链
JavaScript额外知识补充
with语句
- with语句 扩展一个语句的作用域链。
- 不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源。
var obj = { name:"Hello World", age:18 } with(obj){//会形成自己的作用域 console.log(name) console.log(age) }
eval函数的
- eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来运行。
- 不建议在开发中使用eval:
- eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则);
- eval是一个字符串,那么有可能在执行的过程中被刻意篡改,那么可能会造成被攻击的风险;
- eval的执行必须经过JS解释器,不能被JS引擎优化;
var evalString = `var message = "Hello World;console.log(message)"` eval(evalString) console.log(message)
认识严格模式
- 在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode):
- 严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了 ”懒散(sloppy)模式“;
- 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行;
- 严格模式对正常的JavaScript语义进行了一些限制:
- 严格模式通过 抛出错误 来消除一些原有的 静默(silent)错误;
- 严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理);
- 严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法;
开启严格模式
- 那么如何开启严格模式呢?严格模式支持粒度话的迁移:
- 可以支持在js文件中开启严格模式
- 也支持对某一个函数开启严格模式;
- 严格模式通过在文件或者函数开头使用 "use strict " 来开启
"use strict"//开启严格模式 //使用let作为标识符的名称 var name = "abc" console.log(name) //定义变量时不使用var var message = "Hello World" console.log(message)
function foo(){//在函数内开启严格模式 "use strict"; m = "foo" console.log(m) } foo()
严格模式限制
- 这里我们来说几个严格模式下的严格语法限制:
- JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的
- 但是这种方式可能给带来留下来安全隐患
- ;
- 在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正
- 无法意外的创建全局变量
- 严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常
- 严格模式下试图删除不可删除的属性
- 严格模式不允许函数参数有相同的名称
- 不允许0的八进制语法
- 在严格模式下,不允许使用with
- 在严格模式下,eval不再为上层引用变量
- 严格模式下,this绑定不会默认转成对象
//常见的限制 //1.以外创建全局变量,不会生效而是报错 message = "Hello World" console.log(message); //同样的在严格模式下会报错 function foo(){ age = 18 } foo() console.log(age); //2.不允许函数有相同的参数名称 function foo(x,y,x){//两个x就是相同参数名称,如果不开启严格模式,后面的x会将前面的x覆盖掉 console.log(x,y,x); } foo(10,20,30)//30,20,30(非严格模式) //3.静默错误 true.name = "xiaoyu" NaN = 123//非严格模式下不会报错 var obj = {} Object.defineProperty(obj,'name',{ configurable:false,//不可配置 writable:false,//不可写 value:"why" }) console.log(obj.name) obj.name = "xiaoyu"//静默错误,因为我们已经设置不可写入了 //4.不允许使用原先的八进制格式(严格模式) var num = 0123//八进制 var num2 = 0x123//十六进制 var num3 = 0b100//二进制 console.log(num,num2,num3)//Uncaught SyntaxError: Octal literals are not allowed in strict mode //5.eval函数不会向上引用变量 var jsString = "var message = 'Hello World';console.log(message)" eval(jsString) console.log(message)//这里会报错
严格模式下的this
"use strict" //之前编写的代码中,自执行函数我们是没有使用过this直接去引用window的 function foo(){ console.log(this) //通常在自执行函数里面我们想要调用window中的name属性的时候,我们不使用this.name localStorage.setItem//localStorage也会指向window } foo()//正常情况下this指向window,当开启了严格模式后,自执行函数(默认绑定)会指向undefined //setTimeout的this setTimeout(()=>{ console.log(this) },2000)//window setTimeout(function(){ console.log(this) },2000)//非严格模式下是window,严格模式下依旧是window,而不是undefined //this指向window,且是自执行函数,为什么不会是undefined呢?那是因为里面可能执行了一次fn.apply(window),手动指向了this //这个是在浏览器中实现的(伪造fake出来的setTimeout),而不是在v8引擎中实现的
深入JS面向对象
面向对象是现实的抽象方式
现实世界的东西大多数都是可以在编程中抽象出来的
比如你可以抽象出一个女朋友new GridFriend(),或者抽象出coderwhy或者小满什么的来,可惜只能单向的抽象哈哈,不能映射到现实
编程是对现实世界的抽象,而面向对象是对现实世界抽象的一种方式
- 对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
- 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等 等
- 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run) 等等;
- 用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构:
- 所以有一些编程语言就是纯面向对象的编程语言,比Java;
- 你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象
JavaScript的面向对象
- JavaScript其实支持多种编程范式,包括函数式编程和面向对象编程:
- JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
- key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
- 如果值是一个函数,那么我们可以称之为是对象的方法;
- 如何创建一个对象?
- 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:
- 这是因为早期很多JavaScript开发者是从Java过来的,它们也更习惯于Java中通过new的方式创建一个对象;
- 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象:
- 这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来;
创建对象的方式
//创建一个对象,对某个人进行抽象?(描述) //方式1:通过new Object()创建 var obj1 = new Object()//以前的人喜欢这样创建对象 obj1.name = "洛洛" obj1.age = 20 obj1.sex = "女"//然后这样抽象属性 //上面的方式是对obj1当作一个构造函数,然后通过new关键字来执行函数,这个时候也会创建出来对象 //方式2:字面量形式 var obj = { name:"小余", age:20, sex:"男", eating:function(){ console.log(this.name+"在吃辣条~") } }//这种创建对象方式叫做对象的字面量,现在更多是这种
对属性操作的控制
- 在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:
- 但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个 属性是否在for-in遍历的时候被遍历出来呢?
var obj = { name:"小余", age:20, sex:"男" } //对属性的控制 //获取属性 console.log(obj.name)//小余 //给属性赋值 obj.name = "xiaoyu" console.log(obj.name)//xiaoyu //删除属性 delete obj.name console.log(obj)//{ age: 20, sex: '男' }
- 如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。
- 通过属性描述符可以精准的添加或修改对象的属性;
- 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改
Object.defineProperty
- Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
- 会修改我们的原对象的,所以这个并不是一个纯函数
Object.defineProperty(obj,prop,descriptor)
- 可接收三个参数:
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称或者Symbol
- descriptor:要 定义或修改的属性描述符
- 返回值:
- 被传递给函数的对象
var obj = { name:"xiaoyu", age:20 } Object.defineProperty(obj,"height",{ //很多的配置 value:1.75 }) console.log(obj) //node环境下打印 //{ name: 'xiaoyu', age: 20 }
控制台打印:
出现如上问题的原因:
- 因为height的属性是不可枚举,不可遍历的。所以我们在node环境下整体打印就看不到新增的height,但是我们可以局部打印还是可以出来的,例如下方的案例,说明这个height已经真真实实的添加到我们的obj里面了,只是我们看不到而已
var obj = { name:"xiaoyu", age:20 } Object.defineProperty(obj,"height",{ //很多的配置,我们在这里写入的就是属性描述符 value:1.75 }) console.log(obj.height);//1.75
属性描述符分类
- 属性描述符的类型有两种:
- 数据属性(Data Properties)描述符(Descriptor);
- 存取属性(Accessor访问器 Properties)描述符(Descriptor);
configurable(可配置的) |
enumerable(可枚举的) |
value(值) |
writable(可写的) |
get(获取) |
set(设置) |
|
数据描述符 |
可以 |
可以 |
可以 |
可以 |
不可以 |
不可以 |
存取描述符 |
可以 |
可以 |
不可以 |
不可以 |
可以 |
可以 |
数据属性描述符
- 数据属性描述符有如下四个特征
- [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true
- 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false
- [[Enumerable]]:表示属性是否可以通过for-in或者Object.key()返回该属性;
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
- 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false
- [[Writable]]:表示是否可以修改属性的值;
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true
- 当我们通过属性描述符定义一个属性的时候,这个属性的[[Writable]]为false
- [[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
- 默认情况下这个值是undefined
//name和age虽然没有使用属性描述符来定义,但是它们也是具备对应的特性的,以下是对应的默认值 //value:赋值的value //configurable:true //enumerable:true //writable:true var obj = { name:"xiaoyu", age:18 } //数据属性描述符 Object.defineProperty(obj,"address",{ //很多配置 value:"福建省",//默认值undefined //该属性不可删除,不可修改。不可以重新定义属性描述符 configurable:false//默认值false //该特性是配置对应的属性(address)是否是可以枚举的 enumerable:true,//默认值false //该特性是否可以赋值 writable:false//默认值false }) delete obj.name console.log(obj)//{ age: 18 },name被成功删除 delete obj.address console.log(obj.address);//福建省 没删除掉,因为我们设置了不可配置configurable:false //测试enumerable的作用 console.log(obj) for(var key in obj){ console.log(key,'for遍历');//如果enumerable为false,则只会出来name和age,address只有设置为true的时候才会出来 } console.log(Object.keys(obj),'keys的作用'); //enumerable前后对比 //[ 'name', 'age' ] keys的作用(enumerable:false) //[ 'name', 'age', 'address' ] keys的作用(enumerable:true) //测试writable的作用 obj.address = "上海市" console.log(obj.address);//福建省,新的内容不可写入。如果我们不设置value为福建省,则在不可写入的情况下显示undefined
存取属性描述符
- 数据描述符有如下四个特征:
- [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性 描述符
- 和数据属性描述符是一致的
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
- 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
- [[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;
- 和数据描述符是一致的
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
- 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
- [[get]]:获取属性时会执行的函数,默认为undefined
- [[set]]:设置属性时会执行的函数,默认为undefined
var obj = { name:"xiaoyu", age:18, _address:"泉州市"//_开头表示私有的,不希望被人看到。我们就通过get来使用address代替掉_address。别人就通过address调用我们,而不是使用_address调用 } //当我们使用get、set,不使用value和writable的时候,叫做存取属性描述符 //使用场景:1.隐藏某一个私有属性,不希望直接被外界使用和赋值 //2.如果我们希望截获某一个属性,它访问和设置值的过程时,我们也会使用存储属性描述符 Object.defineProperty(obj,"address",{ //很多配置 enumerable:true, configurable:true, //value跟writable与get、set不能共存 // value:"福建省", // writable:true, get:function(){ foo()//我们希望获取值的时候提醒我们一下的时候就会这么干 return this._address//将_address这个属性隐藏起来,给他套上了address这个马甲 }, set:function(value){ bar()//这样就截获了它获取值的过程,这是Vue2响应式的原理 //当我们如下对obj.address进行赋值的时候,值就通过形参传递了进来,我们在这里进行赋值的操作 this._address = value } }) console.log(obj)//{ name: 'xiaoyu', age: 18, address: [Getter/Setter] } console.log(obj.address);//泉州市,get拿到了值 obj.address = "小满的后宫"//我们使用的是address,而不是_address了哦,注意这里的变化 console.log(obj.address);//小满的后宫 function foo(){ console.log("获取了一次address的值") } function bar(){ console.log("设置了一次address的值") }
09_继承的实现方案、ES6面向对象
对象上同时定义多个属性
- Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象。
var obj = { //私有属性(js中没有严格意义上的私有属性,你依旧可以通过obj._age访问到,但是外人是不知道这个隐藏起来的属性,只知道他的替代obj.age) _age:20 } Object.defineProperties(obj,{ name:{ //是否可配置 configurable:true, //是否枚举 enumerable:true, //添加新值 value:"小余", //是否可写入 writable:true }, age:{ configurable:false, enumerable:false, get:function(){ return this._age }, set:function(value){ this._age = value } } }) console.log(obj.age)//20 console.log(obj,"这是为了看age的不可枚举是否生效");//{ _age: 20, name: '小余' } 这是为了看age的不可枚举是否生效 obj.age = 18 console.log(obj.age);//18
- 在开发中,如果我们想对某一个属性定义对应的get和set,我们也可以这么做:
这种写法有一点差异,但是性能是差不多的(但如果我们想要更加精准的控制的话,还是采用在defineProperties写的方式好,因为这样子才能配置configurable这类的东西)
差异在于我们这里控制台打印出来的是能够看到age的,而在Object.defineProperties中是看不到的,在下方代码块中我将他们的对比效果贴出来了,放在最底下
var obj = { _age:20, set age(value){ this._age = value }, get age(){ return this._age } } //以上的age写法就替代了我们在Object.defineProperties的写法 // age:{ // configurable:false, // enumerable:false, // get:function(){ // return this._age // }, // set:function(value){ // this._age = value // } // } //差异对比:控制台打印obj //直接在对象中写法打印效果: { _age: 20, age: [Getter/Setter], name: '小余' }//表示的age属性有get和set //在Object.defineProperties中的get、set打印效果: { _age: 20, name: '小余' }
对象方法补充
- 获取对象的属性描述符:
- getOwnPropertyDescriptor
- getOwnPropertyDescriptors
- 禁止对象扩展新属性:preventExtensions
- 给一个对象添加新的属性会失败(在严格模式下会报错)
- 密封对象,不允许配置和删除属性:seal
- 实际是调用preventExtensions
- 并且将现有属性的configurable:false
- 冻结对象,不允许修改现有属性:freeze
- 实际上是调用seal
- 并且将现有属性的writable:false
var obj = { _age:20, } Object.defineProperties(obj,{ name:{ //是否可配置 configurable:true, //是否枚举 enumerable:true, //添加新值 value:"小余", //是否可写入 writable:true }, age:{ configurable:false, enumerable:false, get:function(){ return this._age }, set:function(value){ this._age = value } } }) //获取某一个特征属性的属性描述符 console.log(Object.getOwnPropertyDescriptor(obj,'name')); //{ value: '小余', writable: true, enumerable: true, configurable: true }这个就是name的配置了 console.log(Object.getOwnPropertyDescriptor(obj,'age')); //{ // get: [Function: get], // set: [Function: set], // enumerable: false, // configurable: false //} //获取对象的所有属性描述符 console.log(Object.getOwnPropertyDescriptors(obj));//请注意这个的区别跟上面那个最后多了一个s // { // _age: { value: 20, writable: true, enumerable: true, configurable: true }, // name: { value: '小余', writable: true, enumerable: true, configurable: true }, // age: { // get: [Function: get], // set: [Function: set], // enumerable: false, // configurable: false // } // }
Object的方法对对象的限制
//禁止对象继续添加新的属性 var obj = { _age:20, } Object.preventExtensions(obj) obj.name = "小余" obj.sex = "男" console.log(obj);//{ _age: 20 },阻止添加成功
//禁止对象配置/删除里面的属性 //麻烦的方式: var obj = { _age:20, name:"小余" } for(var key in obj){ Object.defineProperty(obj,key,{ //将每一个属性都设置为不可配置,包括禁止删除里面的属性 configurable:false, value:key }) } console.log(Object.getOwnPropertyDescriptors(obj));//自己打印出来看看结果,其中的configurable确实都为false了 //简单的方式: Object.seal(obj)//尝试将这行代码注释掉,看前后对比 //验证方式1: delete obj.name console.log(obj.name); //验证方式2: console.log(Object.getOwnPropertyDescriptors(obj));
//让属性不可以修改(相当于让writablel:false) var obj = { //私有属性(js中没有严格意义上的私有属性,你依旧可以通过obj._age访问到,但是外人是不知道这个隐藏起来的属性,只知道他的替代obj.age) _age:20, name:"小余" } Object.freeze(obj) obj.name = "小满zs" console.log(obj);//{ _age: 20, name: '小余' },name没有被修改为"小满zs"
创建多个对象的方案
- 如果我们现在希望创建一系列的对象:比如Person对象
- 包括张三、李四、王五、李雷等等,他们的信息各不相同;
- 那么采用什么方式来创建比较好呢?
- 目前我们已经学习了两种方式:
- new Object方式;
- 字面量创建的方式;
var p1 = { name:"小余", age:20, sex:"男", address:"福建", eating:function(){ console.log(this.name+"在吃烧烤"); }, running:function(){ console.log(this.name+"在跑步做运动"); } } var p2 = { name:"小满", age:23, sex:"男", address:"北京", learn:function(){ console.log(this.name+"在学编程"); }, running:function(){ console.log(this.name+"在跑步做运动"); } } var p3 = { name:"洛洛", age:20, sex:"女", address:"福建", learn:function(){ console.log(this.name+"在学Go语言跟rust语言"); }, running:function(){ console.log(this.name+"在内卷"); } } //以上的方式过于相似,存在过于重复的代码,我们能不能进行优化呢,用另一种方式创建?
- 上面这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码;
创建对象的方案 – 工厂模式
- 我们可以想到的一种创建对象的方式:工厂模式
- 工厂模式其实是一种常见的设计模式;
- 通常我们会有一个工厂方法,通过该工厂方法我们可以产生想要的对象;
function createPerson(){ } //我们虽然里面属性大多数都是相同的,但是数据是不一样的,比如p1的name是小余,p2的是小满,p3的是洛洛,这个时候我们就可以向调用的里面传入参数来实现我们不同数据的传输 var p1 = createPerson() var p2 = createPerson() var p3 = createPerson()
//上面那个空模板的改善方式,传入参数 function createPerson(name,age,sex,occupation,address){ var p = new Object() p.name = name p.age = age p.sex = sex p.occupation = occupation p.address = address p.eating = function(){ console.log(this.name + "在吃满汉全席") } return p } var p1 = createPerson("小余",20,"男","大二学生","福建",) var p2 = createPerson("小满",24,"男","京东程序员","北京") var p3 = createPerson("洛洛",20,"萌妹子","Go+Rust+JavaScript+Node.js全栈工程师兼小余的同学",'福建') console.log(p1,p2,p3); //打印效果如下: // { // name: '小余', // age: 20, // sex: '男', // occupation: '大二学生', // address: '福建', // eating: [Function (anonymous)] // } { // name: '小满', // age: 24, // sex: '男', // occupation: '京东程序员', // address: '北京', // eating: [Function (anonymous)] // } { // name: '洛洛', // age: 20, // sex: '萌妹子', // occupation: 'Go+Rust+JavaScript+Node.js全栈工程师兼小余的同学', // address: '福建', // eating: [Function (anonymous)] // }
工厂函数的缺点
1. 通过上述console.log(p1,p2,p3)打印出来的是我们的字面量 2. 缺少对象应该有的类型,只能看到是Prototype那里的类型都是Object,这种形容过于宽广,分类不够具体。因为人是动物,猫咪也是动物,但两者是不能够混为一谈的。获取不到对象最真实的类型 3. 我们想要达到的效果是:当我们拿到p1、p2、p3的时候,我们还能够知道他们对应的类型,知道这是由什么产生的,而这个是工厂函数没办法做到的事情 //如果你对上面的类型还一知半解,也可以参考我的想法,我认为这是分类过于模糊,我们通过工厂函数创建出来了一个"人",他身上的属性有姓名,身高,职业,性别等等,我们通过工厂函数传入参数也只是对数据进行改变,他本质上的属性是一样的,脱离不了人本身,最多一个叫小余,另一个叫小满,两个不一样的人,但都是人。这个时候我们希望调用工厂函数这个函数的时候,告诉我们类型是人,具体一些,而不是"宇宙中存在的东西",那太过于宽广,实在是跟没说一样,毕竟你抽象出来的东西哪个不是宇宙中的东西对吧
认识构造函数
- 工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型
- 但是从某些角度来说,这些对象应该有一个他们共同的类型;
- 下面我们来看一下另外一种模式:构造函数的方式;
- 我们先理解什么是构造函数?
- 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
- 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
- 但是JavaScript中的构造函数有点不太一样;
- JavaScript中的构造函数是怎么样的?
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
- 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
- 那么被new调用有什么特殊的呢?
new操作符调用的作用
- 如果一个函数被使用new操作符调用了,那么它会执行如下操作:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
function foo(){ var moni = {} this.moni } //new foo相当于会默认执行上面这两个步骤(平时是不显示的,内部自己执行的),然后进行我们的第五步返回创建出来的新对象,不需要我们手写return返回值
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
function foo(){ console.log("foo~"); } var f1 = new foo//foo~ console.log(f1);//foo {} 确实创建出来的一个空对象,且类型就是foo //很明显类型精准了很多,如果上面不够明显,我们可以再来一个案例(输出xiaoyu {},已经很能证明了吧) function xiaoyu(){ console.log("我是小余"); } var f1 = new xiaoyu//我是小余 console.log(f1);//xiaoyu {},node打印结果
- 浏览器控制台打印结果:
function foo(){ console.log("foo~"); } //foo就是一个普通的函数 foo() //换一种方式来调用foo函数,使用操作符new new foo()//一旦我们通过new这样调用,那么这个函数就是一个构造函数了 new foo //我们甚至可以不写这个小括号 //上面3处调用的结果都是foo~,全部成功调用。请注意我说的是3处,也就是上面第四点说的:执行函数的内部代码(函数体代码) //当我们通过new去调用一个函数时,和普通的调用到底有什么区别?
函数什么时候return
这里临时补充一个知识点,突然模糊了什么时候在函数中什么时候要写return?前面有说过,但是好像没写上,这里补一下:
- 在JavaScript中,当你想要从一个函数中返回一个值的时候,就可以使用
return
关键字。
这里的return
的意义是结束函数的执行,并将指定的值作为结果返回给调用函数的代码。例如,你可以这样定义一个函数: - 这个函数接受两个参数,并返回它们的和。你可以调用这个函数,并将返回值赋值给一个变量,例如:
- 在函数内部,你可以使用多个
return
语句,但是只有第一个会被执行,因为一旦执行了return
语句,函数就会立即结束。
如果你希望函数执行完所有的代码后再返回一个值,你可以在函数的最后一行不指定任何返回值,这样函数就会返回一个特殊的值undefined
。
举个例子,假设你有一个函数,它接受一个数字并返回它的平方:
function add(a, b) { return a + b; }
let sum = add(1, 2); // sum的值为3
function square(x) { console.log(x * x); } let result = square(2); // 输出4,result的值为undefined
在这个函数中,我们没有使用return
语句来明确地返回一个值,所以函数会返回undefined
。
创建对象的方案 – 构造函数
- 我们来通过构造函数实现一下:
function xiaoyu(name,age,sex,address){ this.name = name this.age = age this.sex = sex this.address = address this.eating = function(){ console.log(this.name + "在吃鱿鱼须"); } this.runding = function(){ console.log(this.name + "在跟坤坤打篮球"); } } var f1 = new xiaoyu("小余同学",20,"男","福建") console.log(f1); // xiaoyu { // name: '小余同学', // age: 20, // sex: '男', // address: '福建', // eating: [Function (anonymous)], // runding: [Function (anonymous)] // } //然后跟工厂函数一样的,我们可以进行重复描写 var f2 = new xiaoyu("小满zs",23,"男","北京") var f3 = new xiaoyu("洛洛",20,"萌妹子","福建") //很明显,在开头多了一个类型xiaoyu,是不是更加明确清晰了。你想要的任意类型都能够自己更加精准的定位,你写ikun都行
- 这个构造函数可以确保我们的对象是有Person的类型的(实际是constructor的属性,这个我们后续再探讨);
- 但是构造函数就没有缺点了吗?
- 构造函数也是有缺点的,它在于我们需要为每个对象的函数去创建一个函数对象实例;
如何区分是否是构造函数
一般来说,构造函数实在跟普通函数没有区别,单纯看一个函数是看不出来的,于是社区对此有了约定俗成的规范,不是必须要遵守,但拥抱规范能够让我们平时更加方便,减少大家的理解跟沟通成本
function XiaoYu(){ //对于构造函数,我们函数名首字母会是大写。如果由多个单词组成的话,则是采用大驼峰标识 }
构造函数的缺点
- 为什么是f1 === f2是false,那是因为他们创建出来的对象不是同一个对象,虽然打印出来都是[Function: bar],但是确实是不一样的两个对象
- 那为什么不是同一个对象?
- 第一次执行的时候,我们在foo函数里面定义了bar函数,执行foo函数的时候(函数调用),会在内存中创建一个bar的函数对象。然后我们用f1接收了这个函数对象之后,第二次执行会重新创建一个bar函数对象,然后放入f2中。此时是同时存在两个函数对象的
- 它在于我们需要为每个对象的函数去创建一个函数对象实例;(也就是写在开头的那句话
- 每个对象创建一个函数实例会增加内存的开销,这可能会导致性能问题,特别是当你有大量的对象的时候。此外,如果你在构造函数中改变了对象的原型,这也会导致每个对象都有不同的原型,这可能会增加代码的复杂性。
这里创建的函数对象实例为什么会是缺点?
function foo(){ function bar(){ console.log("你猜一不一样"); } return bar } var f1 = foo() var f2 = foo() console.log(f1 === f2);//false
//我们还可以拿刚刚的案例来试一下 function xiaoyu(name,age,sex,address){ this.name = name this.age = age this.sex = sex this.address = address //我们这里的创建对象,相当于在对象里再重复的创建对象其实是没有必要的,就是每个对象的函数去创建一个函数对象实例;为什么这个会是缺点看上面第三点 this.eating = function(){ console.log(this.name + "在吃鱿鱼须"); } this.runding = function(){ console.log(this.name + "在跟坤坤打篮球"); } } var f1 = new xiaoyu("小余同学",20,"男","福建") var f2 = new xiaoyu("小余同学",20,"男","福建") //name其实都是"小余同学",他们的值是没有区别的,造成他们不相等的原因是:每个对象都有不同的原型,所以不相等 console.log(f1.eating === f2.eating);//false console.log(f1.runding === f2.runding);//false
JavaScript高级笔记-coderwhy版本(六)https://developer.aliyun.com/article/1469642