函数三种用法
1.普通函数
2.构造函数
3.对象
function Number(){
}
Number.isNaN = function(){
}
isNaN 这个方法使用时,Number被当做一个普通的对象,因此不能用isNaN(数值),也不能用(2).isNaN,只能用Number.isNaN(数值)来调用
原型上设置的方法,供实例调用,可以写(2).toFixed(2)
阿里面试题,考察new
function Foo(){
getName = function(){
console.log(1)
}
return this;
}
Foo.getName = function(){
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
var getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName() //=> A:(Foo.getName) => new A()
new Foo().getName() //=> B:new Foo() => B.getName()
new new Foo().getName() //=> C:new Foo() => new C[Foo的实例].getName()
优先级
() > . = new
function Fn(){
this.n = 100;
}
Fn.prototype.getN = function(){
console.log(this.n);
}
Fn.AA = 200;
Fn(); // 当做普通函数执行,this是window
var f = new Fn();
jQuery中的原型
~function(){
function jQuery(){
// return [JQ实例]
}
jQuery.prototype.animate = funciton(){}
jQuery.ajax = function(){}
window.jQuery = window.$ = jQuery;
}();
$() // 返回实例
$.ajax()
$(’’)…
写在prototype是供jQuery实例使用,实例不能掉jQuery对象私有的方法
ajax供jQuery对象使用
只要你是函数,不管是哪个类,永远是内置Function这个类的一个实例
Object中有hasOwnProperty方法吗,Object对象没有,他会沿着原型链查找Function.prototype上有没有,Function.prototype上没有,会沿着原型链查找,找到Object.prototype,这里有,于是可以调用
每个函数都可以用__proto__找到 Function.prototype 上面的方法 (call,bind,apply)
面试题(暂时不太理解)
let a = {n : 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)
a.x = (a = {n: 2})
// 赋值运算的本质 是把右边表达式的值 赋值给左边,所以在进行赋值运算之前 计算机必须要先知道你左边和右边都是什么玩意,比如你赋值的时候 1 = 2 计算机先搞清楚了你左边的值是个常量 不能被赋值 他就不执行这一句直接给你报错了,
// 那你在执行 a = a.x = {n: 2} 这对js来说 是一个语句 就算你能把它拆成两个语句 但是js引擎就认为他是一个语句
// 而且是一个赋值语句 所以在执行这一句的时候 他就要知道这个语句的左值和右值是什么玩意
// 在js内部会先执行这一步 依赖于js内部一个操作getValue的操作
// GetValue(a) = GetValue(a.x) = GetValue(n: 2) 这个操作是在赋值之前就执行了
参考地址:思否上面的解答
添加链接描述
csdn另一个解答
添加链接描述
call
当call方法执行时,内部处理了一些事情
=>首先把操作函数中的THIS关键字变成CALL方法第一个传递的实参值
=>把CALL方法第二个及第二个以后的实参获取到
=>把要操作的函数执行,并且把第二个以后的传递进来的实参传给函数
形成一个无限的对象
let a = {n : 1};
let b = a;
a.x = a ;
a = {n:2}
console.log(a.x)
console.log(b.x)
call相关面试题(不会)
function fn1(){
console.log(1)
}
function fn2(){
console.log(2)
}
fn1.call(fn2);
fn1.call.call(fn2);
Function.prototype.call(fn1);
Function.prototype.call.call(fn1);
只记住结论。。
一个call的时候,结果是fn1(),两个call的时候,结果是fn2()
严格模式下的call
非严格模式下,如果参数不传,或者第一个是null/undefined,则This指向window。
严格模式下,第一个参数是谁,This就指向谁
apply , call , bind
apply和call基本一样,区别在于传参,apply传参要传递数组或类数组
bind和call一样,区别在于bind是调用后才执行
现实场景:点击的时候执行FN,让FN中的this是obj
let obj = { name: 'OBJ' }
document.onclick = fn; // this:document
document.onclick = fn.call(obj) // 这时候this改变了,但是绑定的时候就执行了fn,call是立即执行函数,点击的时候执行的是fn的返回值undefined
document.onclick = fn.bind(obj)
获取最大值
方法1:array.sort,取最后一位
方法2: 假设arr[0]最大,循环,如果有更大的,就替换它
方法3: Math.max搭配eval使用
var arr = [1,2,3,44,112,55,312,42]
eval(‘Math.max(’+arr.toString()+’)’)
方法4: Math.max.apply(null,[12,23])
方法5: Math.max(…arr)
fn.apply(obj,[10,20]) => fn(10,20)
虽然这里apply写的是数组,但是相当于给fn一个个传递参数,Math.max中也是这样
括号表达式
let fn = function(){console.log(this);}
let obj = {fn:fn}
(fn,obj.fn)() // 执行的是第二个OBJ.FN,但是方法中的THIS是WINDOW而不是OBJ
(obj.fn)() // this:obj
Array.prototype.slice.call(null,arrayLike)
var arr = [1,2,3]
var test = arr.slice()
test // [1,2,3]
用slice可以克隆一份数组
slice方法就是我们平常用来切割数组用的,之后会返回一个数组.
Array.prototype上面有一些供数组使用的方法,如slice,所以Array.prototype本身也可以调用slice方法,那么Array.prototype.slice.call(arguments,1)这句话的意思就是说把调用方法的参数截取出来。当它调用了slice和call方法后,由于类数组对象也有length,数组索引(0,1,2…),call方法会把它转换为数组
- [].concat.apply([],arrayLike) 用这个方法也可以转化类数组为数组
解构赋值
按照一个数据值的结构,快速解析获取到其中的内容
1.真实项目中一般都是针对于数组或对象进行解构赋值
数组解构赋值注意事项:
let [a, …b, c] = ary; // Uncaught SyntaxError: Rest element must be last element.
剩余运算符必须处于解构中最后的位置
函数中传参的解构赋值(有点复杂且重要)
// 把传递的对象解构了(不传递值,默认赋值为空对象:现在传递对象或者不传递,形参接收到的都是对象),解构的时候,如果某个属性不存在,赋值默认值
let fn = function({name='原始名字',age=0}={}){
console.log(name)
console.log(age)
}
fn({name:'新名字'})
// 新名字
// 0
// 相当于
fn = function(option={}){
let { name, age=0 } = option;
}
小题
let value = { name: ‘xxx’, age: 25, score: [12,23,34,45] }
用解构赋值,使输出:
a = ‘xxx’
b = 12
c = [23,34,45]
let { name:a,score:[b, …c] }
console.log(a,b,c)
… 剩余,拓展和展开运算符
剩余运算符
let ary = [12,23,34,45]
let [a,…b] = ary
这里是剩余运算符
let […arg] = ary
arg = [12,23,34,45]
这里相当于arg复制了ary,但是ary改变arg不会跟着改变
获取函数中第一个参数以后的参数
function fn(context, …arg){
console.log(context, arg); // => ARG是一个数组 / ARGUMENTS是类数组
}
let obj = {};
fn(obj, 10, 20, 30);
展开运算符
let ary = [12,23,45]
Math.max(…ary) // Math.max(12,23,45)
也可以展开obj
let obj = {name: ‘xxx’, age: 20};
let newObj = {…obj, sex:0} //=>{name:‘xxx’,age:20,sex:0} 把原有对象展开(克隆)放到新对象中
let arr = […ary]
写一个复制数组的方法(Array.prototype.slice.call(ArrayLike))
这里注意点在于,for循环里面,写的是this.length;因为我们方法是写在Array.prototype上面,调用的时候是[1,2,3].mySlice(),所以this就是这个数组本身
Array.prototype.mySlice = function() {
let newAry = [];
for (let i = 0; i < this.length; i++) {
newAry.push(this[i]);
}
/*
* 如果我们把内置的slice执行,并且让方法中的this指向arguments,就相当于把arg转换为数组
* 这里注意,ArrayLike可以转化为数组是因为,ArrayLike数据结构有length,有0,1,2索引,所以才可以转换,如果这时候要去转换一个普通对象
* 就会变成Array.prototype.slice.call({}), 也就相当于下面的for循环中,写的是(let i=0;i<{}.length;i++),但{}中并没有length属性
* 所以就会报错
* let ary = [];
* for (let i = 0; i < arguments.length; i++) { 相当于这里变成{}.length
* ary.push(arguments[i]);
* }
* 把ARG这个类数组转换为数组
*/
return newAry
}
let ary = [12, 23, 34];
console.log(ary.mySlice())
借助eval来求数组总和
eval(arr.join('+'))
改写箭头函数
请将let fn = x => y => x + y
写成一般函数
function fn(x){
return function(y){
return x+y
}
}
箭头函数和普通函数的区别
1.箭头函数没有arguments
- 解决方法:用剩余运算符代替arguments
2.箭头函数没有自己的执行主体(THIS),它的THIS都是继承上下文的THIS
箭头函数执行,和是否有点,点前面是谁都没关系
var obj = {
fn:(function(){
return function() {
console.log(this)
}
})()
}
// obj.fn() 这时候的this指向obj,因为是obj来调用的
如果要让obj.fn()指向window,就吧返回的函数改成箭头函数就可以了。
原生js发送ajax请求
let data;
let xhr = new XMLHttpRequest();
xhr.open('get',url,false)
xhr.onreadystatechange = ()=>{
if(xhr.readyState==4&&xhr.status==200){
data = xhr.responseText;
console.log(data)
}
}
xhr.send(null)
获取的结果是一个json格式的字符串
json格式
json不是一种数据类型,而是一种数据格式,只要把对象的属性名用引号括起来,此时的对象就不再称之为普通对象,而是json格式的对象
let obj = { ‘name’ : ‘xiaoming’ } // json格式对象
let str = " { ‘name’ : ‘xiaoming’ }" // json格式字符串
数据绑定
将DOM元素与数据绑定在一起,再将创建好的数据和结构放到页面指定容器中
1.字符串拼接
- 模板引擎(原理也是基于字符串拼接)
- ES6模板字符串拼接
2.动态创建DOM - createElement
- appendChild
Array.sort()
arr.sort((a,b) => {
a: 数组中的当前项
b: 数组中的下一项
return a-b 数组当前项减去下一项,如果结果大于0,就调换位置,所以是升序
})
html结构中添加属性
例如有一个a标签
<a href='url' >
想给他添加一个data-url的属性,如:
<a data-url="newUrl" href="url">
用setAttribute来获取html结构中的属性
document.getElementById(‘s’).setAttribute(‘data-url’,‘xxx’)
如果用 . 或者 [] 来添加属性的话
document.getElementById[‘data-rul’]=‘xxx’
这样无法在getAttribute中获取,在html结构中也不会显示
一道面试题(盛大)
暂时没有想到别的做法
- 在一个字符串数组中有红、黄、蓝三种颜色的球,且个数不相等、顺序不一致,请为该数组排序。使得排序后数组中球的顺序为:黄、红、蓝。
例如:红蓝蓝黄红黄蓝红红黄红,排序后为:黄黄黄红红红红红蓝蓝蓝。
function sortByColor(str){
var obj = {'黄':1,'红':2,'蓝':3}
var arr = a.split('')
arr.sort((a,b)=>{
return obj[a] - obj[b]
})
return arr.join()
}
sortByColor('红蓝蓝黄红黄蓝红红黄红')
// "黄,黄,黄,红,红,红,红,红,蓝,蓝,蓝"
DOM映射机制(之前学过形参映射机制)
页面中的HTML元素,和JS中通过相关方法获取到的元素集合或者元素对象存在映射关系(一个改另一个跟着自动修改)
1.DOM映射,我们修改元素的style属性(修改堆内存),对应的dom样式也发生改变
xxx.style.color = ‘red’ :
把xxx元素对象对应堆内存中的style属性下的color属性修改为’red’(本质操作的是JS堆内存);但是由于DOM映射关系,页面中的标签和XXX元素对象是绑在一起的,我们修改元素对象空间的值,页面中的元素会按照最新的值进行渲染;
2.元素绑定前,我们获取容器中元素,得到一个空的元素集合,元素绑定后,我们不需要重新获取,插入完成后不需要重新获取,元素列表就更新了(不再为空),DOM的映射机制帮助我们将新增的元素映射到之前获取的空集合中,让其变为有元素的集合
let list = document.getElementsByTagName('a');
console.log(list) // null
let str = ''
for(let i = 0;i<3;i++){
str+= '<a href="https://...">测试'
}
document.body.appendChild(str)
console.log(list)
// [nodeList...] 这时候就获取了a元素列表
- 注意点:如果用querySelectAll(),将没有映射机制,即使更新了插入新元素,获取的列表也依然为空。
querySelectorAll获取的集合是静态集合(staticNodeList),因此基于上述方法,数据绑定完成后需要重新获取一次才可以。
appendChild在追加元素对象的时候,如果这个元素之前容器中已经存在,此时不是克隆一份新的追加到末尾,而是把原有的元素移动到末尾位置
箭头函数中没有this,只能根据上下文来判断this,因此无法使用call
var obj = {a:1,b:2}
var a = 'window'
let test = ()=>{
console.log(this.a)
}
test() // window
test.call(obj) // 1
顺便复习,箭头函数没有argument
一道有点难的题
var obj = {
say: function () {
function _say() {
// this 是什么?
console.log(this)
}
return _say.bind(obj)
}() // 这里注意括号,说明声明时就立即执行了
}
obj.say()
//window
我的理解是,当obj在声明的时候,开辟了一个堆内存,obj指针指向这个堆内存中,但还没有把声明语句存到这个堆内存中,因此是不能再里面直接获取到obj的(看下面的例子),由于在声明say方法的时候在}后面加了(),因此会立即执行say这个方法,在say方法中声明了_say这个方法,并且return _say.bind(obj)方法,这时候obj等于是undefined,让方法通过bind绑定undefined,就会让this指针指向window
var obj = {
a:1,
test:(function(){
console.log(obj.a)
})()
}
// 这个时候obj还没执行完,所以当test为立即执行函数时,无法获得obj.a的值
还有一个注意点,在声明函数时这样声明,会声明失败
var a = function(){
console.log(this)
}()
// window
console.log(a) // undefined
基于高级单例模式完成业务逻辑开发
问题1:为什么模块化要写在立即执行函数里面?
不过这样的写法有什么用呢?
javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。
JQuery使用的就是这种方法,将JQuery代码包裹在( function (window,undefined){…jquery代码…} (window)中,在全局作用域中调用JQuery代码时,可以达到保护JQuery内部变量的作用。
let productRender = (function(){
// 自执行函数形成的私有作用域不销毁(“闭包”)
// 1.里面的方法和变量等和外界不冲突
// 2.里面创建的值不会销毁
//getData:基于AJAX从服务端获取数据
let getData = function () {...}
//bindHtml:完成数据绑定
let bindHTML = function () {...}
return {
init: function(){
// init是当前模块的入口,想要实现完整的业务逻辑,我们执行init即可,在init中,我们根据具体的业务需求,规划那些方法先,哪些方法后执行
getData();
bindHMTL();
}
}
})();
productRender.init();
稍微复杂一点的结构赋值
var data = [{name:'xiaoming',age:'22'},{name:'huahua',age:'19'},{name:'laowang',age:'23'}]
data.forEach((item,index)=>{
let {name,age} = item // 解构赋值
console.log(name,age,index)
})
上面的写法可以更精炼一点
data.forEach(({name,age},index)=>{
console.log(name,age,index)
})
// xiaoming 22 0
// huahua 19 1
// laowang 23 2
call,apply有哪些作用
1.改变this
2.转化类数组
3.Math.max不能直接找到数组的最大值,借用apply的机制(不知道怎么借用)
3的写法:Math.max.apply(null, [1,2,3])
类数组不能直接用forEach,因此这样使用会报错
ArrayLike.forEach((a)=>{
console.log(a)
})
改写:
[].forEach.call(ArrayLike,(a)=>{
})
DOM的回流和重绘
- 计算DOM结构(DOM TREE)
- 加载CSS
- 生成渲染书(RENDER TREE),渲染树是和样式相关的
- 浏览器基于GPU(显卡)开始按照RENDER TREE画页面
重绘:当某一个DOM元素样式修改(位置没变只是样式更改)浏览器会重新渲染这个元素
xxx.style.color = ‘red’
xxx.style.fontSize = ‘16px’
上面操作触发了两次重绘,性能上有所消耗,如何优化呢
.xxx{
color:‘red’,
font-size:‘16px’
}
box.className = ‘xxx’
回流:当DOM元素的结构或位置发生改变(删除、增加、位置改变、大小改变…)都会引发回流,回流是指浏览器抛弃原有计算的结构和样式,重新进行DOM TREE或者RENDER TREE,很消耗性能。
回流:
- 重新计算DOM结构=>生成渲染树=>按照RENDER TREE渲染页面
基于文档碎片解决频繁回流的问题,我们可以把需要的元素创建完成,并且都添加到文档碎片中,再统一把文档碎片放到页面中,这样只会引发一次回流操作
// 创建碎片容器,先将要插入的内容插入到碎片容器里面,然后将整个碎片容器插入到box中,这样做只会触发一次回流
let frg = document.createDocumentFragment(); // 创建文档碎片容器
for(var i =0;i<domList.length;i++){
var li = createElement('li')
li = `<div>...</div>`
frg.appendChild(li)
}
box.appendchild(frg)
更新页面的方法
1.字符串拼接
2.DOM操作(上面的方法)
字符串拼接,用es6和for循环拼接,最后用innerHTML = 拼接好后的str,这种方式也只引发1次DOM回流
正则表达式
正则只能用来处理字符串,
处理一般包含两个方面
A: 验证当前字符串是否符合某个规则 “正则匹配”
B: 把一个字符串中符合规则的字符串获取到 “正则捕获”