执行环境-作用域-函数进阶-闭包
1.回顾map的使用和与for/forEach的区别
map的使用-只能用于数组,返回一个新数组,map内部必须return
forEach原数组上进行遍历(本质是for循环),要改变原数组第一层值,必须配合索引
当遍历数组为数组对象用forEach可以直接改变第二层的值
例1 var arr=[1.1,2,3]; var arr1=arr.map(Math.round) //得到一个新数组,每一个值四舍五入 例2 var arr=[1,2,3]; let newArr=arr.map(function(item,index,arr){ return item //遍历得到的新数组,和原数组相同。 })
map和for/forEach的区别:map返回的是新数组,map和forEach不能用break打断,for循环可以。
forEach不能循环执行异步,for可以!!!!!!
2.什么是执行环境
在编写js代码和执行的时候就会产生相应的执行环境 和变量对象(保存设置的变量和对象);
在全局下定义变量和对象都属于全局执行环境,在函数内定义的属于函数执行环境,他们在运行的时候都会加载到环境执行域(执行栈)中---先入后出(执行完后环境会被销毁)
3.作用域和作用域链
1.什么是作用域--面试题
"标识符"能被访问的的范围,称之为作用域
标识符--var 声明的变量、function声明的函数、函数中的形参
访问的范围--自己的作用域和子作用域
2.什么是作用域链
在执行环境中有多层嵌套关系的执行环境就是作用域链。
3.作用域链的作用
确保对执行环境中变量和函数的有序访问。
4.作用域分类
全局作用域(window)、局部作用域(函数)、块级作用域(es6新增)
块级作用域:let 声明的变量只能在块级作用域里内访问//只在声明的块级作用域{}内有效。
{ let a=1 } //外面访问不到a ,换成var 就可以访问
重新定义 var let const的区别:
var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
函数进阶
4.箭头函数
let 变量=(形参)=>{};
【注意】:
1)如果只有一个形参,小括号可以省略。
2)如果函数体内只有一句执行代码,花括号可以省略,但是花括号省略后,函数体内如果有return也必须省略。
3)箭头函数没有arguments对象。
4)箭头函数中的this指向定义它时的作用域。
5.形参和实参
形参:定义函数时,传入的参数
实参:调用函数时传入的参数
6.arguments对象(实际是一个伪数组,es5语法中的)
什么是arguments?
arguments是函数中的内置对象,是一个伪数组,接收函数调用时传进来的实参。可以用索引访问参数,也可以用.length来查询参数的个数。--arguments原型指针指向构造函数Object
【注意】箭头函数中没有arguments对象
var fn=function(){ console.log(arguments) //得到一个伪数组 console.log(arguments[0]) //得到1 console.log(arguments.length) //得到3 } fn(1,3,4)
7.rest参数(实际就是一个数组,es6语法中的)
rest参数指代所有的形式参数,如果前面有定义好名字的形参,那么它就代表除了那个参数以外的其他所有参数。(它必须是最后一个形参) 只能写在函数形参中
语法:let 变量名=(...变量名)=>{} //在函数中可以直接使用定义的rest参数
例: let fn=(...val)=>{ console.log(val) //打印得到调用函数时传入参数的集合,一个数组 } fn(1,3,4)
8.函数的返回值
任何函数都有返回值。如果没有定义return ,默认返回undefined
9.函数的调用
1)普通的调用
2)作为对象中的函数调用
3)作为回调函数调用
4)作为构造函数调用
5)使用bind()/call()/apply()方法调用
6)立即执行函数表达式(IIFE)调用
10.IIFE立即执行函数表达式(常用于插件开发)
语法:;(function(){})() //必须在括号前写上分号,防止报错
什么是立即执行函数??---面试题
1)专业术语:立即执行函数表达式
2)写法:;(function(){})() //第一个()将函数 变成表达式,第二个()调用了这个函数。
3)IIFE的作用,通过形成函数局部作用域 ,避免外界直接访问变量,实现简单的模块化,和let作用一样。
4)一般优先使用let。
11.闭包
什么是闭包??--面试题
闭包是一个环境。【函数】和【函数内部跨作用域能访问到的变量】的之和就是闭包。狭义上来说:函数套函数,内部函数调用了父函数内部的变量并返回执行就形成了闭包。广义上来说:全局函数和全局变量也形成了闭包。
本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的作用:使得函数内部可以访问该函数【声明】时所处作用域的变量。让这些变量的值始终保持在内存中。
闭包的特点??
1)变量私有化,避免全局污染
2)内部变量会常驻内存,不会被释放,垃圾回收机制不会将其回收掉,甚至造成内存溢出。
常见的闭包两种写法:
1) 父函数嵌套子函数,把子函数返回
function fn(){ let a=1; return function(){ return a++; } } let fn1=fn() console.log(fn1()) //1 console.log(fn1()) //2 a没有被释放
2)父函数嵌套子函数,把子函数挂载到全局
//闭包可以实现隐藏变量的操作,需要时可以暴露(挂载到全局)来让全局获取,没有暴露就无法访问到隐藏的变量。 ;(function(){ let a=200; let b=300; window.$key={ //将$key对象挂载到全局,从而可以调用对象下的属性和方法 consoleA:function(){ console.log(a) } senda:function(num){ a-=num; console.log(a); } } })() console.log($key.consoleA()) //得到200 console.log($key.senda(10)) //得到190 console.log(b) //会报错 因为没有将他挂载到全局,所以得不到它的值
12.for...of遍历数组
var arr=[1,2,3,4]; for(let val of arr){ console.log(val) //arr中的每一个值 }
面向对象-原型和原型链-this指向
金桔:要做什么找对象-调用对象的函数
面向对象特点:
1)封装
函数封装
对象封装
模块封装
2)继承
原型链继承
类继承
组合寄生(非常推荐)
。。。
3)多态
JS函数对类型没有要求,不同函数得到不同结果,导致多态
严格说,JS的面向对象不存在多态,或者很难实现多态
1.面向对象和面向过程的区别(面试题)
面向过程(pop)
针对程序的运行流程进行编程的思维。
优势:精细把控每一个细节。劣势:代码冗余
理解:小兵思维--什么都亲历亲为。
面向对象(oop)
针对参与程序运行的个体对象进行编程。
优势:可以实现更加复杂的大型项目。
理解:将军思维--什么都安排别人去做。
面向对象创建-本地存储对象--用的时候import ...from...引入
//如果有就取出来,没有就等于空数组。
2.类和实例化对象
1)认识
1.类指拥有相同特征的一类事物的抽象。
2.实例化对象指拥有各自特征,能执行某些动作的个体。
3.通过类可以创建任意多个具有相同属性和方法的对象。
2)创建对象的方法
1.内置对象创建(new字符实例化)
2.字面量创建
3.工厂函数创建--该模式没解决对象识别的问题(都是Object),为了解决这个问题,我们又有了新的模式来实现对象的实例化----构造函数模式
function creatplayer(name,sex,skill){ var obj={}; obj.name=name; obj.sex=sex; obj.skill=skill; obj.sayname=function(){ console.log(obj.name) } return obj; } var player=creatplayer(参数1,参数2,参数3)//调用工厂函数,实现个体创建。 console.log(player)
3.构造函数模式(存在弊端方法过载)
1) 什么是构造函数
构造函数是一种特殊的方法,主要用来创建对象时初始化对象。即为对象成员变量赋值,总与new字符一起使用在创建对象的语句中。通常用来批量化创建对象。
2)构造函数的创建和使用
1.设置函数体
2.将传入的参数挂载到this上。
3.通过new字符实例化成不同的对象。
function CreatPlayer(name,age,skill){ //把参数通过this挂载到函数上 this.name=name; this.age=age; this.skill=skill; this.fun=function(){console.log(111)} } //实例化构造函数创建对象 let obj1=new CreatPlayer(参数1,参数2,参数3) let obj2=new CeatPlayer(参数1,参数2,参数3)
3)构造函数和普通函数的区别
1.首字母大写,大驼峰命名
2.将属性和方法挂载到this上,this指向最终创建的实例化对象。
3.不需要return语句,默认返回新创建的实例化对象。
4)使用new字符创建对象,中间发生了什么?---面试题
1.创建一个新对象。
2.让this指向这个空对象。(即将构造函数的作用域赋给新对象)。
3.执行构造函数中的代码,为这个对象添加属性和方法。
4.将对象返回。
4.原型和原型链【重点】--必考面试题
先了解方法过载概念:
构造函数中有一个缺陷叫方法过载,我们在通过构造函数实例化对象的时候,每个对象都会独立产生一个构造函数中有的函数方法。这样就重复了这个方法,导致内存和运行弊端。
1)什么是原型??---必考面试题
1.构造函数被创建时都会有一个prototype属性,它就是原型对象。里面包含了实例化共享的属性和方法。
2.原型对象中有一个constructor属性,它指向=>构造函数。(实例化出来的对象也会拥有)
3.创建出来的实例化对象,有一个内置的_proto_属性(原型指针),指向=>实例化对应的的原型对象(object),所以通过该属性建立了实例化与原型之间的关系。
通过原型解决方法过载:
function CreatPlayer(name,age,skill){ //将输入内容挂载到this上 this.name=name; this.age=age; this.skill=skill; //这里不写方法,通过函数的原型添加方法。 } //通过构造函数的原型添加方法,实例化出来的对象都会拥有这个方法 CreatPlayer.prototype.fun=function(){console.log(1111)}; let obj1=new CreatPlayer(参数1,参数2,参数3); let obj2=new CreatPlayer(参数1,参数2,参数3);
通过原型给数组内置方法中拓展一个求和方法:
Array.prototype.sun=function(){ let res=0; this.forEach((item)=>{ res+=item; }) return res } let arr=[1,2,3,4]; console.log(arr.sun()) //调用自己定义的求和方法
2)什么是原型链---必考面试题
1.一个构造函数拥有对应的原型对象,而这个原型对象又可以是另一个构造函数所创建的实例对象(它也有对应的原型对象),这种关系层层渐进,所构成的链式结构,就是原型链。
2.万物皆对象,万物皆空。
3.一个对象中查找标识符时,会从自身开始,没找到就会沿原型链开始逐层向上找,知道找到为止。
5.this指向
1)全局中的this ---指向window
2)普通函数中的this --谁调用指向谁
3)对象中函数的this ---谁调用指向谁
4)事件处理函数中的this ---指向事件源
5)构造函数中的this ---指向实例化的对象
6)定时器中的this ---指向widow
7)箭头函数中的this ---总是指向声明这个箭头函数的作用域
(箭头函数根本没有自己的this,导致内部的this就是外层代码块的this)
//箭头函数中this的使用 var id=20; function logId(){ window.setTimeout(()=>{ console.log(this.id) },1000) } logId.call({id:43}) //此箭头函数绑定了定义时所在的作用域,logId的函数作用域下,所以this才指向了{id:42}
8)bind call 和apply 修改this指向
区别:bind是绑定操作,非立即执行。call和apply都是立即执行,只是传入参数的方式不同(面试题);
1.call改变this指向
对象1.属性名(属性值是函数).call(对象2,参数1,参数2);
2.apply改变this指向
对象1.属性名(属性值是函数).apply(对象2,[参数1,参数2]);
3.bind绑定来改变this指向
let 变量名=对象1.属性名(属性值是函数).bind(对象2,参数1,参数2);
变量名() //调用这个新函数
let obj1={ name:"小胖", say:function(text){ console.log(`${name}说${text}`) } } let obj2={ name:"大胖" } //利用call改变this指向 obj1.say.call(obj2,"我说啥了?11") //利用apply改变this指向 obj1.say.apply(obj2,["我说啥了?222"]); //利用bind来改变this指向 let saywhat=obj1.say.bind(obj2,"我说啥了?333"); saywhat(); //调用这个新函数,bind的时候参数可以不写,调用的时候传入参数
变量进阶,深浅拷贝
1.基本数据类型和引用数据类型
1)基本数据类型:字符串,数字,布尔,null,undefined,symbol()
2)引用数据类型:object(对象,数组,函数)
数据在内存上的分布----面试题
1)基本数据类型:临时保存它的变量和它的具体数据都存放在栈里面。
2)引用数据类型:临时保存它的变量和指引路径存在栈里,它的真实数据放在堆里面。
2.变量的拷贝
1)基本数据类型的拷贝:拷贝的是具体的值。
2)引用数据类型的拷贝:拷贝的是路径。
1.新的变量拷贝的是旧变量的路径指引,他们会共享一个真实数据,只要其中一个变量对数据做了修改,那么另一个变量使用的也会是更改后的数据。
2.如果在拷贝以后,再给新变量定义一个对象,那么它将拥有一个新的路径指向新的真实数据。整体就和旧变量没有任何关系了。所以再改变新的变量下的属性值,旧的变量始终不会变。
例:
var obj1={ name:"张三", age:20 } var obj2=obj1;//仅仅只是拷贝了引用地址 obj2={name:"王五",age:10} //重新定义了obj2,它更新了地址,指向了新的真实数据。和obj1没有任何关系了。 obj2.name="马六" consle.log(obj1) //obj1不再收obj2的影响
3.形式参数的拷贝
1)形参传入基本类型:拷贝的是实际的值。
2)形参传入引用类型:拷贝的是路径。
var obj={ name:"张三", age:20 } function run(num){ num.name="王五" } run(obj) //将obj作为参数传入函数,就是拷贝了obj的路径,调用函数后改变了name值,obj也会改变。 console.log(obj) //王五 20
4.对象的深浅拷贝---面试题
1)深浅拷贝只能出现在对象上面
2)什么是浅拷贝??
书面:只是拷贝一层属性的值,如果遇到多层属性(引用数据类型)拷贝的是它的路径。
白话:就是把一个对象中属性的值为基本数据类型的数据直接拷贝过去。把属性名对应的值为引用数据类型的路径做一个拷贝。
3)什么是深拷贝??
书面:无限层级拷贝。拷贝后,新旧对象不共享引用数据类型属性的值,新旧对象变更互不影响。
白话:深拷贝可以这样理解,如果属性名对应的值为引用数据类型的时候,用深拷贝方法相当于把这个引用数据的值新开一个路径来保存,所以拷贝以后的变量再修改属性数据的时候,不会影响原来对象的数据(因为路径不同了)。
5.实现对象拷贝的方法
1)浅拷贝--(只能拷贝一层,如果遇到拷贝的对象属性有引用类型,拷贝的是地址)
1.for...in方式
2.Object.assign //原生对象下的内置方法
语法:let 新对象=Object.assign({},源对象1,源对象2...) //源对象可以有多个 如果有相同属性,合并后取后者
例1 var obj={name:"王五",skill:"玩"} var obj1={name:"张三",age:20} let obj2=Object.assign({},obj,obj1) //得到新对象 张三 20 玩 例2 //Object.assign为原型对象添加方法 Object.assign(person.prototype,{ getNum:function(){}, getNum2:function(){} })
3.es6语法:let newobj={...oldobj}
2)深拷贝--拷贝多层
JSON.stringify()和JSON.parse()的组合使用
var obj1={name:"张三",age:20,child:{name:"小强",age:6}} let obj2=JSON.parse(JSON.stringify(obj1));
6.变量类型检测
1)typeof ---检测基本数据类型
语法:typeOf(检测体)
2)instanceOf ---检测类型为Object的话都是true 因为万物皆对象
语法:检测体 instanceof 预测类型 //返回true 或false
3)isArray ---预判是不是数组,属于Array下的内置方法 和isNaN类似
语法:Array.isArray(数组) //返回true
4)Object.prototype.toString.call() 主流检测方法
语法:Object.prototype.toString.call(检测体)
var obj={}; var arr=[]; Object.prototype.toString.call(obj);//object Object Object.prototype.toString.call(arr);//object Array
7.Array.from()
Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。浅拷贝-拷贝一层。
拷贝数组:循环/slice/运算符/from/concat
function f3(){ console.log(Array.from(arguments)) } f3(1,2,3) // [1,2,3]
ES6语法-解构赋值-展开运算符-class类-ES6模块化
1.解构赋值
按照一定模式,从数组和对象中提取值,对变量进行赋值。
以=分割,左边变量和右边赋值结构相同,按照顺序一一对应。
1)数组解构赋值--位置不可变,根据【索引】赋值
语法:let [变量1,变量2,变量3=值3]=[值1,值2...] //左边可以提前赋值,右边对应的值可写可不写
2)对象解构赋值--位置可变,根据【属性名】赋值
2.展开运算符
就是把一个对象展开
【注意】因为展开运算符就是三个点,所以要和rest参数区别。(后者只能在函数的形参中使用)
用法举例:
例1 let a=[1,2,3] let b=[4,5,6] let c=[...a,...b] //合并数组 例2 function f(a,b,c){ console.log(a,b,c) } let arr=[1,2,3] f(...arr) 例3 Array.prototype.push.apply(a,b) // a=[1,2,3,4,5,6] a.push(...b) // a=[1,2,3,4,5,6]
可以使用展运算符做数组或对象的拷贝 ---浅拷贝
用扩展运算符对数组或对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层及其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或数组仍然引用的时同一个地址,其中一方改变,另一方也跟着改变。
var obj1={name:"张三",age:20} var obj2={...obj1}
3. set 不重复的集合
//数组去重
Let arr=[1,2,2,3,3,5,6]
Console.log([…new Set(arr)])
4.class类--es6
什么是类?基于原型的面向对象语言。可以认为类就是构造函数的另一种写法。
class关键字定义类
class person { //构造器,传参使用,没有参数可以不写 constructor(name, age) { this.name = name, this.age = age } sex = 200 Show() { console.log(123) }//原型方法,实例化对象共享 static show1() { console.log(456) } //静态方法,类独有 } let obj = new person("张三",20) //实例化
class 类的继承
class Father{ //父类--鱼类 constructor(name,age){ //构造器 不传参可以不写 this.name=name this.age=age } skills=["游泳","睡觉","吃食"] getFatherSkill(){ //原型方法 console.log(this.skills) } } class Son extends Father{ //子类--花鲢--拥有父类所有属性方法 constructor(name,age){ //ES6 要求,子类的构造函数必须执行一次super函数,为了拿父类的this super(name,age) //调用父类构造函数 拿父类this } color="blure" //定义类的私有属性 } const lian1=new Son("花鲢1",4) lian1.skills.push("装死") console.log(lian1) //添加了装死 const lian2=new Son("花鲢2",6) console.log(lian2) //没有装死 ,所以继承的子类实例化出来的对象互不影响
5.ES6模块化
方式1
导出:
export 变量;
多个变量可用export{变量1,变量2...}
推荐:export {arr,fn}
引入:
import{变量}from "路径"; //.js文件名后缀可以省略
方式2
导出:
export default xxx(变量,函数...) //一个js文件只能使用一次
export default sayHi
引入:
import xxx from "路径";//xxx可以是自己命名的名字
import hello from "./js/login" hello()
标签:函数,对象,let,log,javascript1,拷贝,name From: https://www.cnblogs.com/sclweb/p/17628780.html