珠峰笔记 (原型深入,this,正则表达式)

函数三种用法

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的回流和重绘

  1. 计算DOM结构(DOM TREE)
  2. 加载CSS
  3. 生成渲染书(RENDER TREE),渲染树是和样式相关的
  4. 浏览器基于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: 把一个字符串中符合规则的字符串获取到 “正则捕获”

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值