共计 12538 个字符,预计需要花费 32 分钟才能阅读完成。
原型批改、重写
function Person(name) {this.name = name}
// 批改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {getName: function() {}}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
能够看到批改原型的时候 p 的构造函数不是指向 Person 了,因为间接给 Person 的原型对象间接用对象赋值时,它的构造函数指向的了根构造函数 Object,所以这时候p.constructor === Object
,而不是p.constructor === Person
。要想成立,就要用 constructor 指回来:
Person.prototype = {getName: function() {}}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
什么是作用域链?
首先要理解作用域链,当拜访一个变量时,编译器在执行这段代码时,会首先从以后的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到持续向上查找,直到全局作用域为止,,而作用域链,就是有以后作用域与下层作用域的一系列变量对象组成,它保障了以后执行的作用域对合乎拜访权限的变量和函数的有序拜访。
await 到底在等啥?
await 在期待什么呢? 一般来说,都认为 await 是在期待一个 async 函数实现。不过按语法阐明,await 期待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有非凡限定)。
因为 async 函数返回一个 Promise 对象,所以 await 能够用于期待一个 async 函数的返回值——这也能够说是 await 在等 async 函数,但要分明,它等的理论是一个返回值。留神到 await 不仅仅用于等 Promise 对象,它能够等任意表达式的后果,所以,await 前面理论是能够接一般函数调用或者间接量的。所以上面这个示例齐全能够正确运行:
function getSomething() {return "something";}
async function testAsync() {return Promise.resolve("hello async");
}
async function test() {const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
await 表达式的运算后果取决于它等的是什么。
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算后果就是它等到的货色。
- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞前面的代码,等着 Promise 对象 resolve,而后失去 resolve 的值,作为 await 表达式的运算后果。
来看一个例子:
function testAsy(x){return new Promise(resolve=>{setTimeout(() => {resolve(x);
}, 3000)
}
)
}
async function testAwt(){let result = await testAsy('hello world');
console.log(result); // 3 秒钟之后呈现 hello world
console.log('cuger') // 3 秒钟之后呈现 cug
}
testAwt();
console.log('cug') // 立刻输入 cug
这就是 await 必须用在 async 函数中的起因。async 函数调用不会造成阻塞,它外部所有的阻塞都被封装在一个 Promise 对象中异步执行。await 暂停以后 async 的执行,所以 ’cug” 最先输入,hello world’ 和‘cuger’是 3 秒钟后同时呈现的。
object.assign 和扩大运算法是深拷贝还是浅拷贝,两者区别
扩大运算符:
let outObj = {inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
Object.assign():
let outObj = {inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
能够看到,两者都是浅拷贝。
- Object.assign()办法接管的第一个参数作为指标对象,前面的所有参数作为源对象。而后把所有的源对象合并到指标对象中。它会批改了一个对象,因而会触发 ES6 setter。
- 扩大操作符(…)应用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,然而它会复制 ES6 的 symbols 属性。
事件触发的过程是怎么的
事件触发有三个阶段:
window
往事件触发处流传,遇到注册的捕捉事件会触发- 流传到事件触发处时触发注册的事件
- 从事件触发处往
window
流传,遇到注册的冒泡事件会触发
事件触发一般来说会依照下面的程序进行,然而也有特例,如果给一个 body
中的子节点同时注册冒泡和捕捉事件,事件触发会依照注册的程序执行。
// 以下会先打印冒泡而后是捕捉
node.addEventListener(
'click',
event => {console.log('冒泡')
},
false
)
node.addEventListener(
'click',
event => {console.log('捕捉')
},
true
)
通常应用 addEventListener
注册事件,该函数的第三个参数能够是布尔值,也能够是对象。对于布尔值 useCapture
参数来说,该参数默认值为 false
,useCapture
决定了注册的事件是捕捉事件还是冒泡事件。对于对象参数来说,能够应用以下几个属性:
capture
:布尔值,和useCapture
作用一样once
:布尔值,值为true
示意该回调只会调用一次,调用后会移除监听passive
:布尔值,示意永远不会调用preventDefault
一般来说,如果只心愿事件只触发在指标上,这时候能够应用 stopPropagation
来阻止事件的进一步流传。通常认为 stopPropagation
是用来阻止事件冒泡的,其实该函数也能够阻止捕捉事件。
stopImmediatePropagation
同样也能实现阻止事件,然而还能阻止该事件指标执行别的注册事件。
node.addEventListener(
'click',
event => {event.stopImmediatePropagation()
console.log('冒泡')
},
false
)
// 点击 node 只会执行下面的函数,该函数不会执行
node.addEventListener(
'click',
event => {console.log('捕捉')
},
true
)
其余值到布尔类型的值的转换规则?
以下这些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• “”
假值的布尔强制类型转换后果为 false。从逻辑上说,假值列表以外的都应该是真值。
代码输入后果
console.log(1)
setTimeout(() => {console.log(2)
})
new Promise(resolve => {console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {console.log(5)
new Promise(resolve => {resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {console.log(7)
})
console.log(8)
输入后果如下:
1
3
8
4
2
5
6
7
代码执行过程如下:
- 首先执行 script 代码,打印出 1;
- 遇到第一个定时器,退出到宏工作队列;
- 遇到 Promise,执行代码,打印出 3,遇到 resolve,将其退出到微工作队列;
- 遇到第二个定时器,退出到宏工作队列;
- 遇到第三个定时器,退出到宏工作队列;
- 继续执行 script 代码,打印出 8,第一轮执行完结;
- 执行微工作队列,打印出第一个 Promise 的 resolve 后果:4;
- 开始执行宏工作队列,执行第一个定时器,打印出 2;
- 此时没有微工作,继续执行宏工作中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其退出到微工作队列;
- 执行微工作队列,打印出 6;
- 执行宏工作队列中的最初一个定时器,打印出 7。
代码输入后果
Promise.resolve().then(() => {console.log('1');
throw 'Error';
}).then(() => {console.log('2');
}).catch(() => {console.log('3');
throw 'Error';
}).then(() => {console.log('4');
}).catch(() => {console.log('5');
}).then(() => {console.log('6');
});
执行后果如下:
1
3
5
6
在这道题目中,咱们须要晓得,无论是 thne 还是 catch 中,只有 throw 抛出了谬误,就会被 catch 捕捉,如果没有 throw 出谬误,就被继续执行前面的 then。
组件之间的传值有几种形式
1、父传子
2、子传父
3、eventbus
4、ref/$refs
5、$parent/$children
6、$attrs/$listeners
7、依赖注入(provide/inject)
说一说什么是跨域,怎么解决
因为浏览器出于平安思考,有同源策略。也就是说,如果协定、域名或者端口有一个不同就是跨域,Ajax 申请会失败。为来避免 CSRF 攻打
1.JSONP
JSONP 的原理很简略,就是利用 <script> 标签没有跨域限度的破绽。通过 <script> 标签指向一个须要拜访的地址并提供一个回调函数来接收数据当须要通信时。<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {console.log(data) } </script>
JSONP 应用简略且兼容性不错,然而只限于 get 申请。2.CORS
CORS 须要浏览器和后端同时反对。IE 8 和 9 须要通过 XDomainRequest 来实现。3.document.domain
该形式只能用于二级域名雷同的状况下,比方 a.test.com 和 b.test.com 实用于该形式。只须要给页面增加 document.domain = 'test.com' 示意二级域名都雷同就能够实现跨域
4.webpack 配置 proxyTable 设置开发环境跨域
5.nginx 代理跨域
6.iframe 跨域
7.postMessage
这种形式通常用于获取嵌入页面中的第三方页面数据。一个页面发送音讯,另一个页面判断起源并接管音讯
如何提⾼ webpack 的打包速度?
(1)优化 Loader
对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,而后对 AST 持续进行转变最初再生成新的代码,我的项目越大,转换代码越多,效率就越低。当然了,这是能够优化的。
首先咱们 优化 Loader 的文件搜寻范畴
module.exports = {
module: {
rules: [
{
// js 文件才应用 babel
test: /\.js$/,
loader: 'babel-loader',
// 只在 src 文件夹下查找
include: [resolve('src')],
// 不会去查找的门路
exclude: /node_modules/
}
]
}
}
对于 Babel 来说,心愿只作用在 JS 代码上的,而后 node_modules
中应用的代码都是编译过的,所以齐全没有必要再去解决一遍。
当然这样做还不够,还能够将 Babel 编译过的文件 缓存 起来,下次只须要编译更改过的代码文件即可,这样能够大幅度放慢打包工夫
loader: 'babel-loader?cacheDirectory=true'
(2)HappyPack
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特地是在执行 Loader 的时候,长时间编译的工作很多,这样就会导致期待的状况。
HappyPack 能够将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来放慢打包效率了
module: {
loaders: [
{
test: /\.js$/,
include: [resolve('src')],
exclude: /node_modules/,
// id 前面的内容对应上面
loader: 'happypack/loader?id=happybabel'
}
]
},
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory'],
// 开启 4 个线程
threads: 4
})
]
(3)DllPlugin
DllPlugin 能够将特定的类库提前打包而后引入。这种形式能够极大的缩小打包类库的次数,只有当类库更新版本才有须要从新打包,并且也实现了将公共代码抽离成独自文件的优化计划。DllPlugin 的应用办法如下:
// 独自配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 想对立打包的类库
vendor: ['react']
},
output: {path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]-[hash]'
},
plugins: [
new webpack.DllPlugin({
// name 必须和 output.library 统一
name: '[name]-[hash]',
// 该属性须要与 DllReferencePlugin 中统一
context: __dirname,
path: path.join(__dirname, 'dist', '[name]-manifest.json')
})
]
}
而后须要执行这个配置文件生成依赖文件,接下来须要应用 DllReferencePlugin
将依赖文件引入我的项目中
// webpack.conf.js
module.exports = {
// ... 省略其余配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包进去的 json 文件
manifest: require('./dist/vendor-manifest.json'),
})
]
}
(4)代码压缩
在 Webpack3 中,个别应用 UglifyJS
来压缩代码,然而这个是单线程运行的,为了放慢效率,能够应用 webpack-parallel-uglify-plugin
来并行运行 UglifyJS
,从而提高效率。
在 Webpack4 中,不须要以上这些操作了,只须要将 mode
设置为 production
就能够默认开启以上性能。代码压缩也是咱们必做的性能优化计划,当然咱们不止能够压缩 JS 代码,还能够压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,咱们还能够通过配置实现比方删除 console.log
这类代码的性能。
(5)其余
能够通过一些小的优化点来放慢打包速度
resolve.extensions
:用来表明文件后缀列表,默认查找程序是['.js', '.json']
,如果你的导入文件没有增加后缀就会依照这个程序查找文件。咱们应该尽可能减少后缀列表长度,而后将呈现频率高的后缀排在后面resolve.alias
:能够通过别名的形式来映射一个门路,能让 Webpack 更快找到门路module.noParse
:如果你确定一个文件下没有其余依赖,就能够应用该属性让 Webpack 不扫描该文件,这种形式对于大型的类库很有帮忙
懒加载与预加载的区别
这两种形式都是进步网页性能的形式,两者次要区别是一个是提前加载,一个是缓慢甚至不加载。懒加载对服务器前端有肯定的缓解压力作用,预加载则会减少服务器前端压力。
- 懒加载也叫提早加载,指的是在长网页中提早加载图片的机会,当用户须要拜访时,再去加载,这样能够进步网站的首屏加载速度,晋升用户的体验,并且能够缩小服务器的压力。它实用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的实在门路保留在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出实在门路赋值给图片的 src 属性,以此来实现图片的提早加载。
- 预加载指的是将所需的资源提前申请加载到本地,这样前面在须要用到时就间接从缓存取资源。 通过预加载可能缩小用户的等待时间,进步用户的体验。我理解的预加载的最罕用的形式是应用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。
为什么 0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到相似这样的问题:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
这里失去的不是想要的后果,要想等于 0.3,就要把它进行转化:
(n1 + n2).toFixed(2) // 留神,toFixed 为四舍五入
toFixed(num)
办法可把 Number 四舍五入为指定小数位数的数字。那为什么会呈现这样的后果呢?
计算机是通过二进制的形式存储数据的,所以计算机计算 0.1+0.2 的时候,实际上是计算的两个数的二进制的和。0.1 的二进制是0.0001100110011001100...
(1100 循环),0.2 的二进制是:0.00110011001100...
(1100 循环),这两个数的二进制都是有限循环的数。那 JavaScript 是如何解决有限循环的二进制小数呢?
个别咱们认为数字包含整数和小数,然而在 JavaScript 中只有一种数字类型:Number,它的实现遵循 IEEE 754 规范,应用 64 位固定长度来示意,也就是规范的 double 双精度浮点数。在二进制迷信表示法中,双精度浮点数的小数局部最多只能保留 52 位,再加上后面的 1,其实就是保留 53 位有效数字,残余的须要舍去,听从“0 舍 1 入”的准则。
依据这个准则,0.1 和 0.2 的二进制数相加,再转化为十进制数就是:0.30000000000000004
。
上面看一下 双精度数是如何保留 的:
- 第一局部(蓝色):用来存储符号位(sign),用来辨别正负数,0 示意负数,占用 1 位
- 第二局部(绿色):用来存储指数(exponent),占用 11 位
- 第三局部(红色):用来存储小数(fraction),占用 52 位
对于 0.1,它的二进制为:
0.00011001100110011001100110011001100110011001100110011001 10011...
转为迷信计数法(迷信计数法的后果就是浮点数):
1.1001100110011001100110011001100110011001100110011001*2^-4
能够看出 0.1 的符号位为 0,指数位为 -4,小数位为:
1001100110011001100110011001100110011001100110011001
那么问题又来了,指数位是正数,该如何保留 呢?
IEEE 标准规定了一个偏移量,对于指数局部,每次都加这个偏移量进行保留,这样即便指数是正数,那么加上这个偏移量也就是负数了。因为 JavaScript 的数字是双精度数,这里就以双精度数为例,它的指数局部为 11 位,能示意的范畴就是 0~2047,IEEE 固定 双精度数的偏移量为 1023。
- 当指数位不全是 0 也不全是 1 时(规格化的数值),IEEE 规定,阶码计算公式为 e-Bias。此时 e 最小值是 1,则 1 -1023= -1022,e 最大值是 2046,则 2046-1023=1023,能够看到,这种状况下取值范畴是
-1022~1013
。 - 当指数位全副是 0 的时候(非规格化的数值),IEEE 规定,阶码的计算公式为 1 -Bias,即 1 -1023= -1022。
- 当指数位全副是 1 的时候(非凡值),IEEE 规定这个浮点数可用来示意 3 个非凡值,别离是正无穷,负无穷,NaN。具体的,小数位不为 0 的时候示意 NaN;小数位为 0 时,当符号位 s = 0 时示意正无穷,s= 1 时候示意负无穷。
对于下面的 0.1 的指数位为 -4,-4+1023 = 1019 转化为二进制就是:1111111011
.
所以,0.1 示意为:
0 1111111011 1001100110011001100110011001100110011001100110011001
说了这么多,是时候该最开始的问题了,如何实现 0.1+0.2=0.3 呢?
对于这个问题,一个间接的解决办法就是设置一个误差范畴,通常称为“机器精度”。对 JavaScript 来说,这个值通常为 2 -52,在 ES6 中,提供了 Number.EPSILON
属性,而它的值就是 2 -52,只有判断 0.1+0.2-0.3
是否小于Number.EPSILON
,如果小于,就能够判断为 0.1+0.2 ===0.3
function numberepsilon(arg1,arg2){return Math.abs(arg1 - arg2) < Number.EPSILON;
}
console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
理解 this 嘛,bind,call,apply 具体指什么
它们都是函数的办法
call: Array.prototype.call(this, args1, args2])
apply: Array.prototype.apply(this, [args1, args2])
:ES6 之前用来开展数组调用, foo.appy(null, [])
,ES6 之后应用 … 操作符
- New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
- 如果须要应用 bind 的柯里化和 apply 的数组解构,绑定到 null,尽可能应用 Object.create(null) 创立一个 DMZ 对象
四条规定:
- 默认绑定,没有其余润饰(bind、apply、call),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined
function foo() {console.log(this.a);
}
var a = 2;
foo();
- 隐式绑定:调用地位是否有上下文对象,或者是否被某个对象领有或者蕴含,那么隐式绑定规定会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最初一层在调用地位中起作用
function foo() {console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
obj.foo(); // 2
- 显示绑定:通过在函数上运行 call 和 apply,来显示的绑定 this
function foo() {console.log(this.a);
}
var obj = {a: 2};
foo.call(obj);
显示绑定之硬绑定
function foo(something) {console.log(this.a, something);
return this.a + something;
}
function bind(fn, obj) {return function() {return fn.apply(obj, arguments);
};
}
var obj = {a: 2}
var bar = bind(foo, obj);
New 绑定,new 调用函数会创立一个全新的对象,并将这个对象绑定到函数调用的 this。
- New 绑定时,如果是 new 一个硬绑定函数,那么会用 new 新建的对象替换这个硬绑定 this,
function foo(a) {this.a = a;}
var bar = new foo(2);
console.log(bar.a)
什么是 margin 重叠问题?如何解决?
问题形容: 两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。须要留神的是,浮动的元素和相对定位 这种脱离文档流的元素的外边距不会折叠。重叠只会呈现在 垂直方向。
计算准则: 折叠合并后外边距的计算准则如下:
- 如果两者都是负数,那么就去最大者
- 如果是一正一负,就会正值减去负值的绝对值
- 两个都是负值时,用 0 减去两个中绝对值大的那个
解决办法: 对于折叠的状况,次要有两种:兄弟之间重叠 和父子之间重叠(1)兄弟之间重叠
- 底部元素变为行内盒子:
display: inline-block
- 底部元素设置浮动:
float
- 底部元素的 position 的值为
absolute/fixed
(2)父子之间重叠
- 父元素退出:
overflow: hidden
- 父元素增加通明边框:
border:1px solid transparent
- 子元素变为行内盒子:
display: inline-block
- 子元素退出浮动属性或定位
浏览器的次要组成部分
- ⽤户界⾯ 包含地址栏、后退 / 后退按钮、书签菜单等。除了浏览器主窗⼝显示的您申请的⻚⾯外,其余显示的各个局部都属于⽤户界⾯。
- 浏览器引擎 在⽤户界⾯和出现引擎之间传送指令。
- 出现引擎 负责显示申请的内容。如果申请的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- ⽹络 ⽤于⽹络调⽤,⽐如 HTTP 申请。其接⼝与平台⽆关,并为所有平台提供底层实现。
- ⽤户界⾯后端 ⽤于绘制根本的窗⼝⼩部件,⽐如组合框和窗⼝。其公开了与平台⽆关的通⽤接⼝,⽽在底层使⽤操作系统的⽤户界⾯⽅法。
- JavaScript 解释器。⽤于解析和执⾏ JavaScript 代码。
- 数据存储 这是长久层。浏览器须要在硬盘上保留各种数据,例如 Cookie。新的 HTML 标准 (HTML5) 定义了“⽹络数据库”,这是⼀个残缺(然而轻便)的浏览器内数据库。
值得注意的是,和⼤少数浏览器不同,Chrome 浏览器的每个标签⻚都别离对应⼀个出现引擎实例。每个标签⻚都是⼀个独⽴的过程。
对浏览器的了解
浏览器的次要性能是将用户抉择的 web 资源出现进去,它须要从服务器申请资源,并将其显示在浏览器窗口中,资源的格局通常是 HTML,也包含 PDF、image 及其他格局。用户用 URI(Uniform Resource Identifier 对立资源标识符)来指定所申请资源的地位。
HTML 和 CSS 标准中规定了浏览器解释 html 文档的形式,由 W3C 组织对这些标准进行保护,W3C 是负责制订 web 规范的组织。然而浏览器厂商纷纷开发本人的扩大,对标准的遵循并不欠缺,这为 web 开发者带来了重大的兼容性问题。
浏览器能够分为两局部,shell 和 内核。其中 shell 的品种绝对比拟多,内核则比拟少。也有一些浏览器并不辨别外壳和内核。从 Mozilla 将 Gecko 独立进去后,才有了外壳和内核的明确划分。
- shell 是指浏览器的外壳:例如菜单,工具栏等。次要是提供给用户界面操作,参数设置等等。它是调用内核来实现各种性能的。
- 内核是浏览器的外围。内核是基于标记语言显示内容的程序或模块。
v-model 语法糖是怎么实现的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- v-model 只是语法糖而已 -->
<!-- v-model 在外部为不同的输出元素应用不同的 property 并抛出不同的事件 -->
<!-- text 和 textarea 元素应用 value property 和 input 事件 -->
<!-- checkbox 和 radio 应用 checked property 和 change 事件 -->
<!-- select 字段将 value 作为 prop 并将 change 作为事件 -->
<!-- 留神:对于须要应用输入法 (如中文、日文、韩文等) 的语言,你将会发现 v -model 不会再输入法 组合文字过程中失去更新 -->
<!-- 再一般标签上 -->
<input v-model="sth" /> // 这一行等于下一行
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />
<!-- 再组件上 -->
<currency-input v-model="price"></currentcy-input>
<!-- 上行代码是上行的语法糖 <currency-input :value="price" @input="price = arguments[0]"></currency-input> -->
<!-- 子组件定义 -->
Vue.component('currency-input', {
template: `
<span>
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</span>
`,
props: ['value'],
})
</body>
</html>