根据《你不知道的JavaScript》以及随缘想到的问题整理成册
Author:付紫琴
文章目录
如何理解执行上下文?
执行上下文需要感性理解,上下文即承上启下,即当前对象在程序中的一个执行环境;分全局上下文,函数上下文,和特殊的eval上下文,
上下文中包含了变量,作用域和this指针,每一段js执行都有产生自己的上下文.
如何理解作用域链?
在js执行的上下文中包含作用域链,作用域则是根据变量名称查找变量定义的一套规则,当存在块嵌套和函数嵌套时,会一层一层向父级查找,直到查找到全局作用域才会停止.
在编译原理的层面上说:即在使用的地方向上查找该变量在哪里声明的.
其中特殊的两个机制:eval()和with()可以修改变量的词法作用域
前者可以用字符串的形式声明了已经存在的变量,
后者则是通过对对象在with的作用域范围内做左查询来修改变量的值,
但是一旦个值不存在,则会在with的同级作用域内声明一个变量,变量泄漏到全局作用域了
使用这两个都会使引擎编译变慢
块作用域和函数作用域
声明在一个函数内部的变量或函数会
在所处的作用域中“隐藏”起来,这是有意为之的良好软件的设计原则;
但函数不是唯一的作用域单元。块作用域指的是变量和函数不仅可以属于所处的作用域,
也可以属于某个代码块;
有趣的是:块作用域非常有用的原因和闭包及回收内存垃圾的回收机制相关,在块作用域中占大量内存的数据结构会被回收
判断一个运行中函数的 this 绑定
需要找到这个函数的直接调用位置;我们关注这个函数在哪里调用,并非在哪里声明,this的指向只与第一层和最后一层有关;
可以顺序应用下面这四条规则来判断 this 的绑定对象。
- 由 new 调用?绑定到新创建的对象。
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
- 有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑
定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null),以保护全局对象。- 箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这
其实和 ES6 之前代码中的 self = this 机制一样。
var let const的区别
ES5没有块级作用域的概念,ES6新增块级作用域;
- var定义的变量,没有块的概念,可以跨块访问,
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
- 不能跨函数访问。var存在变量提升,可以在定义前使用,值为undefined,let和const不会
console.log(fuziqin)
> VM105:1 Uncaught ReferenceError: fuziqin is not defined at <anonymous>:1:13 (anonymous) @ VM105:1
console.log(fuziqin1);
var fuziqin1=1
> undefined
- var是顶层变量,在全局声明的变量,var声明的变量会同时是顶层对象的属性,但是let与const不是。(浏览器内,顶层对象是windw,node是global,采用this在两个环境内都可)
var var1 = 1;
console.log(this.var1);//var1
let let1 = 1;
console.log(this.let1) // undefined
let const1 = 1;
console.log(this.const1) // undefined
什么是闭包
当函数可以记住并访问当前作用域时,就产生了闭包,即使函数在当前作用域外执行;
因为JavaScript的垃圾自动回收机制,在块作用域外,不再使用的内存空间将会被回收,但是闭包却可以阻止该事件的发生,该内部函数作用域一直存活,直到在作用域外被调用声明时的词法作用域
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包,在几微秒之后变量 baz 被实际调用(调用内部函数 bar)这个函数在定义时的词法作用域以外的地方被调用。
function foo() {
var a = 2;
function bar() {
// 拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2
延伸 模块模式(模块暴露)
- 必须有外部封闭函数,该函数至少呗调用一次(每次调用会产生一个模块实例)
- 封闭函数至少返回一个内部函数,这样才能再封闭函数内形成闭包,并可以访问和修改私有内容
特点:为函数定义引入包装函数,并保证它的返回值和模块的 API 保持一致
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
//modules.name指向函数并向里面传值
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
call/apply/bind作用和区别
三个方法都可以改变this指向(是this的硬绑定),第一个参数都是this的指向对象
- bind返回的一个function,可以执行,也可以new实例
- call和bind接收参数都是逗号分隔的单个的对象,而apply接收的是数组参数,并在内部实现展开传入函数
/** 手写call/apply/bind **/
<script>
Function.prototype.myCall = function(context) {
if(typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window;
// 获取函数
context.fn = this;
// 获取参数
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window;
// 获取函数
context.fn = this;
// 获取参数
let result
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
return result
}
Function.prototype.myBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window;
const _self = this;
const args = [...arguments].slice(1);
return function F(){
console.log(new.target,111111111111)
if(this instanceof F) {
return new _self(...args,...arguments)
} else {
_self.apply(context,args.concat(...arguments))
}
}
}
function a(...b){console.log(1,b)}
let b = a.myBind({a:1},[1,2,3])
new b()
</script>
延伸:函数柯里化
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
- 参数复用
- 提前确认
- 延迟运行
封装方式参考bind,差不多
缺点
- 存取arguments对象通常要比存取命名参数要慢一点
- 一些老版本的浏览器在arguments.length的实现上是相当慢的
- 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
- 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上
什么是深/浅拷贝,有哪些实现方式
浅拷贝是copy对象的引用,改变引用地址的值,则该复制对象也改变
深拷贝是彻底的复制该对象
浅拷贝的实现方式
- 直接赋值
- Object.assign() // 一层是深拷贝,多层是浅拷贝
- ES6扩展运算符 // let newObj={ …obj }
- jQuery.extend方法 let newObj = jQuery.extend({}, obj);
- 类数组对象拷贝 Array.from(array1) / Array.concat() / Array.slice()等等
深拷贝的实现方式
- var newObj = JSON.parse(JSON.stringify(obj));
- jQuery.extend方法 var newObj = jQuery.extend(true, {}, obj);
- Object.assign() //一层是深拷贝,多层是浅拷贝
- jQuery.extend第一个参数指示是否深度合并
// $.extend( [deep ], target, object1 [, objectN ] ) - 函数递归调用
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
var obj = { a:1, arr: [1,2] };
var newObj = deepCopy(obj);
obj.arr[0] = 10;
console.log(newObj.arr[0]); // 1
如何理解原型链?
提到原型链就不得不理解prototype,prototype就是对象中的一种内部链接,他会引用其他对象;如果在对象上没有找到需要的属性或者方法引用,引擎就
会继续在 [[Prototype]] 关联的对象上进行查找。这一点和作用域很像.
每个函数都有prototype属性,指向一个对象,即函数的原型;
每个函数都有实例对象__proto__属性,而这个实例对象__proto__指向函数的prototype,
当我们查找一个实例的属性或者方法时,会先去他的构造函数里面查找,如果没有就去__proto__原型(1)中查找,即父级函数的prototype,而这个父级函数中依然没有会一直向上查找,object.prototype.__proto__为null,即原型链的终点.
// child.proto == parent.prototype
(1)对__proto__进行延展,笨蛋Proto其实也不是一个属性,
是和constructor一样内置在prototype中,
__proto__更像是构造调用了获取一个对象的 [[Prototype]] 链方法 Object.getPrototypeOf( this)获取了prototype,如下所示
Object.defineProperty( Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf( this );
},
set: function(o) {
// ES6 中的 setPrototypeOf(..)
Object.setPrototypeOf( this, o );
return o;
}
} );
继承有哪些方法
js的继承并不会复制对象属性,而是以委托的形式将父和子中间产生关联,也就是原型链
1. 原型链继承
特点:
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
- 要想为子类新增属性和方法,必须要在Student.prototype = new Person() 之后执行,不能放到构造器中
//父类型
function Person(name, age) {
this.name = name,
this.age = age,
this.play = [1, 2, 3]
this.setName = function () { }
}
Person.prototype.setAge = function () { }
//子类型
function Student(price) {
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person()
// 子类型的原型为父类型的一个实例对象
var s1 = new Student(15000)
var s2 = new Student(14000)
console.log(s1,s2)
2. 借用构造函数继承
特点:
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Person(name, age) {
this.name = name,
this.age = age,
this.setName = function () {}
}
Person.prototype.setAge = function () {}
function Student(name, age, price) {
// 在子类型构造函数中通用call()调用父类型构造函数
Person.call(this, name, age) // 相当于: this.Person(name, age)
/*this.name = name
this.age = age*/
this.price = price
}
var s1 = new Student('Tom', 20, 15000)
3.组合继承(组合原型链继承和借用构造函数继承)(常用)
优点:
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
function Person(name, age) {
this.name = name,
this.age = age,
this.setAge = function () { }
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age, price) {
Person.call(this,name,age)
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person()
Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的
Student.prototype.sayHello = function () { }
var s1 = new Student('Tom', 20, 15000)
var s2 = new Student('Jack', 22, 14000)
console.log(s1)
console.log(s1.constructor) //Student
console.log(p1.constructor) //Person
4. 原型式继承(实例继承)
优点:
- 不限制调用方式
简单,易实现
- 缺点:不能多次继承
function Wonman(name){
let instance = new People();
instance.name = name || 'wangxiaoxia';
return instance;
}
let wonmanObj = new Wonman();
5. 寄生式继承
首先我们复制一份 Vehicle 父类(对象)的定义,
然后混入子类(对象)的定义(如果需要的话保留到父类的特殊引用),然后用这个复合对象构建实例。
// “传统的 JavaScript 类”Vehicle
function Vehicle() {
this.engines = 1;
}
Vehicle.prototype.ignition = function() {
console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
this.ignition();
console.log( "Steering and moving forward!" );
};
// “寄生类” Car
function Car() {
// 首先,car 是一个 Vehicle
var car = new Vehicle();
// 接着我们对 car 进行定制
car.wheels = 4;
// 保存到 Vehicle::drive() 的特殊引用
var vehDrive = car.drive;
// 重写 Vehicle::drive()
car.drive = function() {
vehDrive.call( this );
console.log(
"Rolling on all " + this.wheels + " wheels!"
);
return car;
}
var myCar = new Car();
myCar.drive();
// 发动引擎。
// 手握方向盘!
// 全速前进!
6. 寄生组合式继承(常用)
//父类
function People(name,age){
this.name = name || 'wangxiao'
this.age = age || 27
}
//父类方法
People.prototype.eat = function(){
return this.name + this.age + 'eat sleep'
}
//子类
function Woman(name,age){
//继承父类属性
People.call(this,name,age)
}
//继承父类方法
(function(){
// 创建空类
let Super = function(){};
Super.prototype = People.prototype;
//父类的实例作为子类的原型
Woman.prototype = new Super();
})();
//修复构造函数指向问题
Woman.prototype.constructor = Woman;
let womanObj = new Woman();
7.es6 extend super关键字继承
//class 相当于es5中构造函数
//class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
//class中定义的所有方法是不可枚举的
//class中只能定义方法,不能定义对象,变量等
//class和方法内默认都是严格模式
//es5中constructor为隐式属性
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
//继承父类
class Woman extends People{
constructor(name = 'ren',age = '27'){
//继承父类属性
super(name, age);
}
eat(){
//继承父类方法
super.eat()
}
}
let wonmanObj=new Woman('xiaoxiami');
wonmanObj.eat();
如何准确判断一个对象是数组
- Array.prototype.isPrototypeOf(obj);
- obj instanceof Array
- Object.prototype.toString.call(obj)
- Array.isArray()
数组有哪些常用方法
split()将字符串转化为数组
join() 将数组转化为字符串
栈方法 push() pop()进栈和出栈
队列方法 shift()从队列的前端移除 unshift()在前端增加参数
reverse()反转数组
sort()排序
concat()连接两个数组
slice() 从索引处截取到数组末尾
splice() 往数组中插入或者删除
ES6新增的
Array.of()新增元素-Array(2)是改变size
Array.from() 提供了第二个参数可以映射到返回值
Array.copyWith() fill() find() findIndex() entries() value() keys()等
数组的去重
1. ES6新增set的数据结构是数据无重复的
Array.from(new Set(arr))
2. 双重循环 冒泡遍历相等就splice
3. indexOf 新建一个空数组,遍历旧数组 if(~arr.indexof(arr[i])) arr[i]push进空数组
4. sort()排序,相邻比较
5. 利用对象属性不重复
6. 遍历判断tempList.includes()
7. filter返回数组,object.hasOwnProperty(typeof item+item)
8. filter返回arr.indexof==index indexof返回第一个元素出现的索引
9. sort 在递归
10. 利用map数据结构,new arr
11. reduce+includes arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[])
12. [...new set(arr)]
数组如何排序
多看!!硬着头皮看!!忘了再看就记住了!!!十大经典排序算法
https://www.cnblogs.com/onepixel/articles/7674659.html
1. sort(function(x,y){return x-y})
2. 待完善
谈一下for…of
- for of ES6新增;可以遍历数组对象的每个元素,for of会向对象请求一个迭代器,调用他的next()来遍历所有返回值
for of 可以应用解构,使用对象中的key value,结合map等多种灵活用法; - for in 是遍历出列表的索引 ;会遍历对象的所有可枚举属性;;;
谈一下EventLoop
事件循环 Event Loop
- 同步的进入主线程,异步的进入event table执行注册函数
- 当异步任务完成会进入event queue
- 主线程的任务完成会从event queue中将任务推进主线程执行
- js引擎monitoring process进程会不断在主线程中检查是否为空,一旦为空就会去等待队列中调用等待被调用的函数.
- 上述过程会不断重复.即event loop
例如promise就是基于js的这一时间循环机制解决了异步执行函数的信任问题:比如调用过早,调用过晚,甚至不调用,调用次数过少,参数丢失,未能处理错误和异常这些问题
同步任务 异步任务setTimeout setinterval 宏任务 微任务Promise process.nextTick
JavaScript没有多线程,都是单线程模拟出来的,通过事件循环event loop实现异步
例子:
setTimeout(()=>{
task()
},3000)
console.log(1);
sleep(1000000000000)
setTimeout进入eventtable注册task()
定时进入eventtable,3秒后task进入等待队列 ,同时主线程console=>睡醒了进入等待队列拿task
除开广义的同步和异步还有宏任务和微任务这个概念
script,setTimeout,setinterval这些属于宏任务
promise .then Process.nextNick属于微任务
**理解这个问题的重点是:同步任务和微任务是交替执行的;**
主任务执行完了之后就去微任务队列里拿等待队列,微任务执行完了之后就执行宏任务...
手写promise
https://segmentfault.com/a/1190000020505870
给我读!!!认真读!!!
里面要传入一个resolve方法,和reject方法,其中resolve会对传入的事件进行封装成异步的行为;
怎么使用=>解决了什么问题=>api then catch all race等
=>大致实现=>也存在bug
正则表达式
- 普通字符
/[ABC]/ 匹配其中的字符
/[^ABC]/ 匹配除了其中的字符
/[\s\S]/ \s 匹配空白符包括换行;\S非空白符不包括换行 - 非打印字符 转义字符
\cx \f \n \r等等 - 特殊字符
$ 匹配字符串结尾位置
() 子表达式的开始结束- *前面出现0到多次
- +前面至少一次
? 前面的子表达式0到一次
| 两项中选一个
- 限定符
-
- 贪婪,尽可能多的匹配文字(?非贪婪);
在 、+ 或 ? 限定符之后放置 ?,该表达式从"贪婪"表达式转换为"非贪婪"表达式或者最小匹配。
例如:/<.?>/ /<\w+?>/
{n} 确定的n次
{n,} 至少n次
{n,m} 至少n至多m
- 贪婪,尽可能多的匹配文字(?非贪婪);
-
ES6新增特性
待完善:https://www.runoob.com/regexp/regexp-syntax.html
# ES6更新了哪些东西
1. 语法上新增了块作用域的概念,明确了let是块作用范围的变量;更新了很多语法糖:例如spread/rest表达式,解构赋值,模板字面量实现字符串和变量混合编写,Symbol对象
2. 代码组织 迭代器,生成器
3. 异步流控制promise
4. 集合set map这样的数据结构
5. 新增的API
# webpack
##### 为什么要使用webpack
webpack将文件看成一个一个的模块,传统的项目加载多个js需要多次进行http请求,现使用webpack就只有一个入口一个出口,减少了请求压力,增加了打包和请求效率
1.默认打包js资源
2.html插件将以html为模板生成一个html供浏览器使用
3.img 依赖fileloader urlloader(还可以引入其他资源,对fileloader进行封装)
4.css styleloader cssloader
5.babel将高级语言转化成低版本浏览器可以识别的语言,polyfill将新更新的语法以补丁的形式打包出来供浏览器使用
6.等等
还没看完待完善
## transform transition动画和animation有什么区别
CSS变形(transform)呈现的仅仅是一个“结果”,而CSS过渡(transition)呈现的是一个“过程”。
(1)对于transition属性来说,它只能将元素的某一个属性从一个属性值过渡到另一个属性值。
(2)对于animation属性来说,它可以将元素的某一个属性从第1个属性值过渡到第2个属性值,然后还可以继续过渡到第3个属性值,以此类推。
从上面我们可以清楚地知道:transition属性(即CSS3过渡)只能实现一次性的动画效果,而animation属性(即CSS3动画)可以实现连续性的动画效果。
## DOM节点创建和修改有哪些常用API
操作元素节点
创建节点:document.createElement
插入节点:appendChild insertBefore
删除节点:remove
复制节点:cloneNode
替换节点:replaceChild
操作属性节点
div.title
getAttribute
操作文本节点
innerHtml
# CSS清除浮动有哪些方法
``` 2. 父级div定义 overflow: auto;使用overflow属性来清除浮动有一点需要注意,overflow属性共有三个属性值:hidden,auto,visible。我们可以使用hiddent和auto值来清除浮动,但切记不能使用visible值,如果使用这个值将无法达到清除浮动效果,其他两个值都可以。 ``` .over-flow{ overflow: auto; zoom: 1; //zoom: 1; 是在处理兼容性问题ie6 } ``` 3. after 方法:(注意:作用于浮动元素的父亲) ``` .outer {zoom:1;} /*==for IE6/7 Maxthon2==*/ .outer:after {clear:both;content:'.';display:block;width: 0;height: 0;visibility:hidden;} /*==for FF/chrome/opera/IE8==*/ ``` # CSS选择器优先级 总结排序:!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性
谈一下flex布局
弹性盒子模型:
定义display:flex让元素变成弹性盒子,子元素宽度之和<=父元素宽度
flex-grow:按比例放大;争取父元素宽度的比例,子元素一般不设置固定宽度以免影响比例分配
flex-shrink:按比例缩小
flex-basis:等价于width,用于flex盒子下的子元素,便于语义化
flex-direction:子元素的排列方向,取值:row/row-reverse/column/column-reverse
flex-wrap:nowrap/wrap/wrap-reverse
order:子元素的排列顺序
justify-content:flex-start/center/flex-end/space-between/space-around定义子元素在横轴上的对齐方式
align-items:flex-start/center/flex-end/baseline/strecth纵轴上的对齐方式
复合属性:flex: grow shrink basis;
复合属性:flex-flow: direction wrap;
CSS实现三列布局(左右固定宽度,中间自适应)
实现的方式有太多种了
1.脱离文档流,浮动
2.使用定位
3.flex布局
4.calc()计算
5.column瀑布用法
等等等
谈一下盒模型
盒模型由里向外是content padding border margin,
有两种:
标准模型:width = content+padding+border
IE模型:width = content
除此之外还有css3新增的flex弹性盒模型和column多列盒模型
box-sizing:content-box//标准 | border-box//IE
GET和POST有什么区别
get和post都是http协议下的两种请求方式
get参数在url,会被浏览器记住在历史记录里,post不会
post参数在requestBody,get比post更不安全
post会发送两个tcp请求包,第一次传送head,第二次传送data;firefox只发送一次,
但是这两次的请求请求速度差不了多少,可以忽略不计
所以基于安全性考虑,get用于请求数据,post用于携带多重信息发送信息
浏览器解析渲染页面过程
构建dom树 html css,加载js,构建render树进行渲染,布局render树,绘制render树,树节点的重绘和重排
H5自适应方案
rem缩放比,upx vw vm这样的单位,可以做到一定的适配
另也有插件lib-flexible、postcss-plugin-px2rem进行rem适配
websocket心跳
vue源码理解
主要实现mvvm双向绑定,vue里面会定义两个主要的对象
- observer
- compiler
- 首先实现observer()将data里面的数据,采用数据劫持结合发布订阅模式,通过object.defineProperty来劫持各个属性的get和set
- get就做了初始化数据的操作
- 实现指令解析器compile对页面的指令进行解析,并new了watcher对象进行订阅数据的变化
- 一旦数据发生变化,就会走observer的set方法,notify通知更新,compile中对指令new的watcher订阅到了消息就会去dep里面找到对应的watcher进行update操作;
到这里就实现了数据更新视图
- 对指令对应的数据进行监听,一旦发生变化就更新数据;
这一步就实现了视图驱动数据
所以watcher就建立了observer和compile的桥梁,做了几件事:
- 在自身实例化的时候往dep里面添加自己
- 有一个update数据的方法
- 在observer数据变化时dep.notify被调用的时候触发compile中绑定的回调
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
观察者和发布订阅者区别
谈一下防抖和节流
谈一下常用设计模式,并选择一个进行场景分析
前端常见攻击方式
前端有哪些跨域方案
前端网站常规优化方案