本文共 12260 字,大约阅读时间需要 40 分钟。
原理
浅拷贝会创建一个新的对象,这个对象有着原始对象属性值的一份精准拷贝:
function clone(origin) { var result = { }; for (var prop in origin) { if (origin.hasOwnProperty(prop)) { result[prop] = origin[prop]; } } return result;}var jay = { name: "jayChou", age: 40, family: { wife: "Quinlivan" }}var otherJay = clone(jay);otherJay.age = 18;otherJay.family.wife = "otherGirl";console.log(jay); // // { // name: "jayChou",// age: 40, // 没被改变// family: { // wife: "otherGirl" // 同时被改变,说明是同一个引用// }// }console.log(otherJay);// // { // name: "jayChou",// age: 18,// family: { // wife: "otherGirl" // 被改变了// }// }
实现浅拷贝的方式
Object.assign():该方法将所有可枚举的自身属性从一个或多个源对象复制到目标对象。它返回目标对象。
Object.assign(新对象, ...源对象)
Array.Prototype.concat():该方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
let arr2=arr.concat(); //将arr浅拷贝,生成arr2
Array.Prototype.slice():该方法返回一个新的副本对象,该对象是一个由 begin
和end
决定的原先的浅拷贝(包括begin
,不包括end
,左闭右开)。原始序列不会被改变。
let arr = [1, 3, { username: 'kobe'} ];let arr3 = arr.slice(); //将arr浅拷贝到arr3arr3[2].username = 'wade'console.log(arr); //wade
原理
完全复制另外一个对象,引用也是自己创建。即完整复制舒服的值(而非引用)。目的在于避免拷贝后数据对原数据产生影响
实现深拷贝的方式
JSON方法实现:利用JSON的parse()和stringfy()实现对某一个对象的深拷贝(无法处理源对象中的函数)
let arr = [1, 3, { username: 'kobe'} ];let arr4 = JSON.parse(JSON.stringify(arr));arr4[2].username = 'james'; console.log(arr, arr4) //[1,3,{username:'kobe'}] [1,3,{username:'james'}]
手写递归方法:递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
//定义检测数据类型的功能函数function checkedType(target) { return Object.prototype.toString.call(target).slice(8, -1)} //实现深度克隆---对象/数组 function clone(target) { //判断拷贝的数据类型 //初始化变量result 成为最终克隆的数据 let result, targetType = checkedType(target) if (targetType === 'object') { result = { } } else if (targetType === 'Array') { result = [] } else { return target } //遍历目标数据 for (let i in target) { //获取遍历数据结构的每一项值。 let value = target[i] //判断目标结构里的每一值是否存在对象/数组 if (checkedType(value) === 'Object' || checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组 //继续遍历获取到value值 result[i] = clone(value) } else { //获取到value值是基本的数据类型或者是函数。 result[i] = value; } } return result}
函数库lodash:该函数库也有提供 _.cloneDeep用来做深拷贝。
var _ = require('lodash');var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3]};var obj2 = _.cloneDeep(obj1);console.log(obj1.b.f === obj2.b.f); // false
含义
对于高频触发的函数,我们并不想频发触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,这个时候就需要对函数增加防抖功能了
代码
含义
在某个规定的时间内,节流函数至少执行一次。
代码
typeof:返回的是数据类型的字符串表达
var a;console.log(typeof a) // 'undefined'
instanceof:是否是该构造函数的实例
理解数据类型:
undefined和null的区别?
undefined代表未定义,null代表定义了未赋值。
什么时候给变量赋值为null?
显式原型与隐式原型
每个函数都有一个prototype属性,它默认指向一个Object实例空对象(原型对象),原型对象中有一个属性constructor,它指向函数对象。
每个函数function都有一个prototype,即显式原型(属性);每个实例对象都有一个 _ _proto _ _,可称为隐式原型(属性)。
实例对象的隐式原型的值为其构造函数的显式原型的值。
原型链
属性问题
instanceof
A instanceof B:A是否是B这个构造函数的实例对象
A:实例对象
B:构造函数
如果函数B的显式原型在A对象的原型链上,返回true,否则返回false。
面试题
function A(){ }A.prototype.n=1;var b = new A();//在原型中,= 一定要理解成 指向A.prototype = { n:2, m:3}var c = new A();console.log(b.n,b.m,c.n,c.m); //1,undefined,2,3
function F(){ }Object.prototype.a=function(){ console.log('a()')}Function.prototype.b=function(){ console.log('b()')}var f= new F()f.a() // a()f.b() // error F.a() // a()F.b() // b()
变量提升
全局执行上下文
函数执行上下文
执行上下文栈
面试题1
面试题2
知识点:先执行变量提升,再执行函数提升!
function a(){ }var a;console.log(typeof a) // 'function'
面试题3
if(!(b in window)){ var b = 1;}console.log(b); //undefined
面试题4
var c = 1;function c(c){ console.log(c); var c = 3;}c(2) //error 先变量提升 后函数提升
理解
分类
作用
隔离变量,不同作用域下同名变量不会有冲突!
作用域和执行上下文区别
联系
作用域链
面试题1
面试题2
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
闭包的个数 = 调用外部函数的次数
闭包到底是什么?
注:执行函数定义就会产生闭包(不用调用内部函数)
常见的闭包
将函数作为另外一个函数的返回值
将函数作为实参传递给另一个函数调用
闭包的作用
两个问题
函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在,存在于闭包中的变量才可能存在,但也必须要有引用指向该函数。
在函数外部能直接访问函数内部的局部变量吗?
不能,但我们可以通过闭包的形式让外部操作它
闭包的生命周期
闭包应用:自定义JS模块
JS模块:具有特定功能的JS文件
将所有的数据和功能都封装在一个函数内部
只向外暴露一个包含n个方法的对象或函数
模块的使用者,只需要通过模块暴露的对象调用方法来实现相应功能
方式1:
function myModule(){ //私有数据 var msg = 'My atguigu' //操作数据的函数 function doSomething(){ console.log('doSomething') } function doOtherthing(){ console.log('doOtherthing') } //向外暴露 return { doSomething:doSomething, doOtherthing:doOtherthing }}
方式2:
(function myModule(){ //私有数据 var msg = 'My atguigu' //操作数据的函数 function doSomething(){ console.log('doSomething') } function doOtherthing(){ console.log('doOtherthing') } //向外暴露 window.myModele2 = { doSomething:doSomething, doOtherthing:doOtherthing }})()
内存泄漏
内存溢出
面试题1
面试题2
方式1:Object构造函数模式
方式2:对象字面量模式
方式3:工厂模式
function createPerson(name,age){ var obj = { name:name, age:age, setName: function(name){ this.name=name } } return obj;}
方式4:自定义构造函数模式
function Person(name,age){ this.name=name; this.age=age; this.setName=function(name){ this.name=name; }}var p1 = new Person('Tom',12);p1.setName('Jack');console.log(p1.name,p1.age); function Student(name,price){ this.name=name this.price=price}var s = new Student('Bob',13000)console.log(s instanceof Student)
方式5:构造函数+原型的组合模式
function Person(name,age){ this.name=name; this.age=age;}Person.prototype.setName = function(name) { this.name = name;}
原型链继承
//父类型function Father(){ this.supProp = 'father property'}Father.propertype.show = function(){ console.log(this.supProp)}//子类型function Son(){ this.subProp = 'son property'}Son.prototype = new Father() // 这是关键,子类型的原型为父类型的实例Son.prototype.constructor = Son // 让子类的原型的constructor指向子类型Son.propertype.show2 = function(){ console.log(this.subProp)}var son = new Son()son.show() // father property
借用构造函数继承
function Person(name,age){ this.name = name; this.age = age;}function Student (name,age,price){ Person.call(this,name,age) //相当于 this.Person(name.age) // this.name = name; // this.age = age; this,price = price;}var s = new Student('Tom',20,14000)console.log(s.name,s.age,s.price);
组合继承
function Person(name,age){ this.name = name; this.age = age;}Person.prototype.setName = function(name){ this.name = name;}function Student (name,age,price){ Person.call(this,name,age) //相当于 this.Person(name.age) // this.name = name; // this.age = age; this.price = price;}Student.prototype = New Person() // 为了能看到父类的方法Student.prototype.constructor = Student //修正constructor属性Student.prototype.setPrice = function (price){ this.price = price}var s = new Student('Tom',24,15000)s.setName('Bob')s.setPrice(16000)console.log(s.name,s.age,s.price) // Bob 24 16000
function Person(name,age,sex,job){ this.name = name; this.age = age; this.sex = sex; this.job = job; } Person.prototype.eat = function (){ console.log(`我是${ this.job}${ this.name},我正在吃东西`) } function Boss(name,age,sex,job,thing){ Person.call(this,name,age,sex,job); this.thing=thing } Boss.prototype = new Person(); Boss.prototype.constructor = Boss; Boss.prototype.bossThing = function (thing){ this.thing=thing console.log(`我是${ this.job}${ this.name},我要${ this.thing}`) } function manager(name,age,sex,job,thing){ Person.call(this,name,age,sex,job); this.thing=thing } manager.prototype = new Person(); manager.prototype.constructor = manager; manager.prototype.managerThing = function (thing){ this.thing=thing console.log(`我是${ this.job}${ this.name},我要${ this.thing}`) } function worker(name,age,sex,job,thing){ Person.call(this,name,age,sex,job); this.thing=thing } worker.prototype = new Person(); worker.prototype.constructor = worker; worker.prototype.workerThing = function (thing){ this.thing=thing console.log(`我是${ this.job}${ this.name},我要${ this.thing}`) } var xiaolong = new Boss('WXL',21,'男','老板','打员工'); var donglin = new manager('QDL',22,'男','管理层',''); var zhixiong = new worker('李智雄',22,'男','员工',''); xiaolong.bossThing('打老婆') xiaolong.eat() donglin.managerThing('骂人') donglin.eat() zhixiong.workerThing('辞职回家种田') zhixiong.eat()
进程
线程
相关知识
浏览器是单进程还是多进程
内核模块
JS引擎模块:负责js程序的编译与运行
html,css文档解析模块:腐恶页面文本的解析
DOM/CSS模块:负责DOM/CSS在内存中的相关处理
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
(运行在主线程上)
…………………………………………………………………………………………………………
(运行在分线程上)
定时器模块:负责定时器的管理
事件响应模块:负责事件的管理
网络请求模块:负责ajax请求
定时器真是定时执行的吗?
定时器回调函数是在分线程执行的吗?
在主线程执行的,js是单线程的
定时器是如何实现的?
事件循环模型(后面讲)
如何证明js执行时单线程的?
为什么js要用单线程模式,而不是多线程模式?
Javascript的单线程,与它的用途有关作为,浏览器脚本语言,Javascript的主要用途是与用户交互,以及操作DOM,这决定了它只能是单线程,否则会带来很复杂的同步问题。
代码的分类
js引擎执行代码的基本流程
模型原理图
模型的2个重要组成部分:
模型的运转流程:
转载地址:http://cfox.baihongyu.com/